ReactJS: Functional component with props defined as Array but seen as Object - reactjs

When rendering <MyComponent {...docs} />, I kept the following error:
TypeError: docs.map is not a function
Here is how I am rendering <MyComponent /> from a parent class based component:
import * as React from 'react'
import IDoc from '../types/IDoc'
class Doc extends React.Component
{
public render()
{
const docs : IDoc[] = Defs.getDef();
// Example of map working (not doing anything just for an example)
docs.map(x => x);
return (
<div>
<MyComponent {...docs} />
</div>
)
}
}
For some reason when I pass the docs array to the functional <MyComponent/> component, its not seen as an array. I need to convert it to an Array before using .map() which I would prefer to avoid:
import * as React from 'react'
import IDoc from '../types/IDoc'
// docs is an array isn't?
function MyComponent(docs : IDoc[] )
{
if (Array.isArray(docs) === false)
{
//Its not seen as an array so falls into this
return (
<div>
{ Object.keys(docs).map((index) => {
const doc : IDoc = docs[index];
const name = doc.name;
return (
<div>{name}</div>
)
})
}
</div>
)
}else
{
// what I expected to work but it throws the error
return (
<div>
{ docs.map((doc) => {
return (
<div>{doc.name}</div>
)
})
}
</div>
)
}
}
I thought as I defined docs props as IDocs[] it would have been seen as an array due to the square brackets.
The workaround above works, but obviously I don't want to do this every time I use an array function like map(). I am new to React so would appreciate any pointers. I used create-react-app my-app --scripts-version=react-scripts-ts in case that is helpful.

Currently you're spreading the elements of the docs array into the props of MyComponent by doing this:
<MyComponent {...docs} />
Consider revising your MyComponent function so that docs is accessed via it's own prop. This would mean accessings docs via the props object passed to the MyComponent functional component like so:
/* Add props argument, where docs array is an entry of props */
function MyComponent(props : { docs : IDoc[] }) {
const { docs } = props;
/* You can now use docs as before
if (Array.isArray(docs) === false)
*/
}
This change would require that in your Doc component, the MyComponent component is rendered by passing docs as a prop (rather than spreading the elements of the docs array directly into the props of MyComponent) by doing the following:
return (
<div>
{/* Don't spread docs, but instead pass docs via docs prop */}
<MyComponent docs={docs} />
</div>
)
Hope this helps!

Related

React you cannot pass props inside a regular function but you can pass a prop through a class component

I tried making a class and pass the prop named input under the Time component and the prop renders the prop name however I do the same but instead of using a class I use a function named Kite and try to do the same thing but I get an object error in the console and nothing doesn't render on the page is there a way I can pass a prop through a regular function instead of a class?
function Kite() {
return (<h1>{this.props.kiteprop}</h1>)
}
class Time extends React.Component {
render() {
return (
<div>
<Test input="dsa" />
<Kite kiteprop="showtext" />
<div>{this.props.input}</div>
<span>{new Date().toLocaleDateString()}</span>
</div>
);
}
}
ReactDOM.render(<Time input="joe" input="[1, 2]" input="false" />, document.getElementById("root"));
A better syntax would be:
const Kite = (props) => {
return(<h1>{props.kiteprop}</h1>)
}
export default Kite;
And seperating the components into 2 pages of js.
Notice that we pass props into the params of our component, and no need to use this here.

Results are not populating after fetching from API using React

I am working with some examples to fetch the data from an API using fetch. But, it is returning nothing in view. What i am doing wrong? Why it is not populating? Here is a link to the code.
Application code here:
import React, { Component } from 'react';
import { render } from 'react-dom';
import Hello from './Hello';
import axios from 'axios';
import './style.css';
const url='https://10degrees.uk/wp-json/wp/v2/posts';
class App extends Component {
constructor() {
super();
this.state = {
name: 'React',
searchInput: 'tirur',
avatarDatas: []
};
this.fetchData = this.fetchData.bind(this);
}
componentDidMount(){
this.fetchData(url);
}
fetchData = (apiToFetch)=>{
fetch(apiToFetch)
.then(result=>result.json())
.then(avatarData=>{
this.setState({
avatarDatas : avatarData
})
})
.catch((error) => console.log(error));
}
render() {
console.log(this.state.avatarDatas)
return (
<div>
<Hello name={this.state.name} />
<p>
{this.state.avatarDatas.map(item=>{
<div>{item}</div>
})}
</p>
</div>
);
}
}
render(<App />, document.getElementById('root'));
Your code is fetching data from the remote URL correctly. However, you're using the map function in your render method wrong. You have:
{this.state.avatarDatas.map(item=>{
<div>{item}</div>
})}
This does indeed map through all the entries in avatarDatas, but the function the callback you've provided the map isn't returning anything. You've written a function block with a JSX statement in it, and no return statement. What you should have written is:
{this.state.avatarDatas.map(item=>{
return (<div>{item}</div>);
})}
or even this, where there isn't an actual function block but just a return value:
{this.state.avatarDatas.map(item=>
<div>{item}</div>
)}
At this point, the avatarDatas still won't be listed, because the JSX in your map callback is trying to have item rendered. item is an object, and React won't know how to convert it to a string. Rather do this:
{this.state.avatarDatas.map(item=>
<div>{item.title.rendered}</div>
)}
(Or any other of the many fields that each avatarData has.)
Finally, React may still issue a warning, saying that each item in a list must have a unique key. Whenever you use map to create a number of elements, React expects you to give a unique identifier to each element, which it will use to identify that element when it needs to update your list during rerendering. That means that you should do this:
{this.state.avatarDatas.map((item, index) =>
<div key={index}>{item.title.rendered}</div>
)}
Now, your map callback assigns an index to each <div> and all is well with the world.

React.memo - why is my equality function not being called?

I have a parent component that renders a collection of children based on an array received via props.
import React from 'react';
import PropTypes from 'prop-types';
import shortid from 'shortid';
import { Content } from 'components-lib';
import Child from '../Child';
const Parent = props => {
const { items } = props;
return (
<Content layout='vflex' padding='s'>
{items.map(parameter => (
<Child parameter={parameter} key={shortid.generate()} />
))}
</Content>
);
};
Parent.propTypes = {
items: PropTypes.array
};
export default Parent;
Every time a new item is added, all children are re-rendered and I'm trying to avoid that, I don't want other children to be re-rendered I just want to render the last one that was added.
So I tried React.memo on the child where I'll probably compare by the code property or something. The problem is that the equality function never gets called.
import React from 'react';
import PropTypes from 'prop-types';
import { Content } from 'components-lib';
const areEqual = (prevProps, nextProps) => {
console.log('passed here') // THIS IS NEVER LOGGED!!
}
const Child = props => {
const { parameter } = props;
return <Content>{parameter.code}</Content>;
};
Child.propTypes = {
parameter: PropTypes.object
};
export default React.memo(Child, areEqual);
Any ideas why?
In short, the reason of this behaviour is due to the way React works.
React expects a unique key for each of the components so it can keep track and know which is which. By using shortid.generate() a new value of the key is created, the reference to the component changes and React thinks that it is a completely new component, which needs rerendering.
In your case, on any change of props in the parent, React will renrender all of the children because the keys are going to be different for all of the children as compared to the previous render.
Please reference this wonderful answer to this topic
Hope this helps!
I was having the same issue and the solution turned out to be just a novice mistake. Your child components have to be outside of the parent component. So instead of:
function App() {
const [strVar, setStrVar] = useState("My state str");
const MyChild = React.memo(() => {
return (
<Text>
{strVar}
</Text>
)
}, (prevProps, nextProps) => {
console.log("Hello"); //Never called
});
return (
<MyChild/>
)
}
Do it like this:
const MyChild = React.memo(({strVar}) => {
return (
<Text>
{strVar}
</Text>
)
}, (prevProps, nextProps) => {
console.log("Hello");
});
function App() {
const [strVar, setStrVar] = useState("My state str");
return (
<MyChild strVar = {strVar}/>
)
}
Another possibility for unexpected renders when including an identifying key property on a child, and using React.memo (not related to this particular question but still, I think, useful to include here).
I think React will only do diffing on the children prop. Aside from this, the children prop is no different to any other property. So for this code, using myList instead of children will result in unexpected renders:
export default props => {
return (
<SomeComponent
myLlist={
props.something.map(
item => (
<SomeItem key={item.id}>
{item.value}
</SomeItem>
)
)
}
/>
)
}
// And then somewhere in the MyComponent source code:
...
{ myList } // Instead of { children }
...
Whereas this code (below), will not:
export default props => {
return (
<SomeComponent
children={
props.something.map(
item => (
<SomeItem key={item.id}>
{item.value}
</SomeItem>
)
)
}
/>
)
}
And that code is exactly the same as specifying the children prop on MyComponent implicitly (except that ES Lint doesn't complain):
export default props => {
return (
<SomeComponent>
{props.something.map(
item => (
<SomeItem key={item.id}>
{item.value}
</SomeItem>
)
)}
</SomeComponent>
)
}
I don't know the rest of your library but I did some changes and your code and (mostly) seems to work. So, maybe, it can help you to narrow down the cause.
https://codesandbox.io/s/cocky-sun-rid8o

Conditional rendering question with ReactJS

I'm trying to use a <Conditional if={condition}> component in React, that renders its content only if the condition is true.
I've used the code from here, as mentioned in Broda Noel's response of this question. You also see the code here.
import * as React from 'react';
const Conditional = props => (
!!props.if && props.children
);
class Item extends React.Component {
render() {
const item = { id: 2 };
return (
<>
<h2>Item detail</h2>
{/* Display item.foo.name if `foo` property exists */}
<Conditional if={item.foo}>
{item.foo.name}
</Conditional>
</>);
}
}
export default Item;
It fails with the error:
Uncaught TypeError: Cannot read property 'name' of undefined
I know I can also use {item.foo && item.foo.name}, but when the code becomes more complex, I find the use of Conditional more readable. And most important, I would really like to understand what happens here.
Why does React render the content of the Conditional, even is the condition is false?
Is there a way to change the Conditional component to make it work in this case?
I'm using React 16.4.1, with typescript.
The code:
<Conditional if={item.foo}>
{item.foo.name}
</Conditional>
is transpiled to:
React.createElement(Condition, { if: item.foo }, item.foo.name)
So even if the condition is false it will try to access the property name.
I suggest that you use the code below when the code becomes more complex:
import * as React from 'react';
class Item extends React.Component {
renderConditional (item) {
if (!item.foo) {
return null;
}
return item.foo.name; // Or some jsx
}
render() {
const item = { id: 2 };
return (
<>
<h2>Item detail</h2>
{this.renderConditional(item)}
</>
);
}
}
export default Item;
You could also create another component to present the data and check the object inside the new component.
The problem is that your component <Conditional> have no logical expression in the if prop. The "React way" to do this is using conditional rendering as is:
render() {
const item = { id: 2 };
return (
<>
<h2>Item detail</h2>
{ item.foo !== undefined && // React Way to Conditional Rendering
{item.foo.name}
}
</>
);
}
I recommend you to read the docs for Conditional Rendering

key is not available as prop in the Child component in ReactJs

I have a parent component in which below component is producing dynamically in map function as below:
const renderListing = this.props.listing.map(function(list, index) {
return (
<Listing
key={index}
title={list.title}
totalWorkers={list.totalWorkers}
/>
);
}, this);
In this <Listing /> component, I have a Checkbox from react-md as below:
import { Checkbox } from "react-md";
class Listing extends React.Component {
render() {
return (
<Checkbox id="`listSector-${this.props.key}`" name="list-sector" />
);
}
}
I wanted to give the props named key concatenated with id="listSector-0" , id="listSector-1" and so on.
I have tried string-literals but all invain.
Can anyone know that how to give dynamic this.props.key concatenated with id of the checkbox ?
Thanks
'key' and 'ref' are reserved words that are not allowed to be passed as props. You can pass another prop with the same value
const renderListing = this.props.listing.map(function(list, index) {
return (
<Listing
key={index}
keyId={index}
title={list.title}
totalWorkers={list.totalWorkers}
/>
);
}, this);
and use it like
<Checkbox id="`listSector-${this.props.keyId}`" name="list-sector" />
Most props on a JSX element are passed on to the component, however, there are two special props (ref and key) which are used by React, and are thus not forwarded to the component.
Special Props Warning – React

Resources