I'm trying to render a component, but the value is inside a jQuery function that checks for changes in an input field.
The console returns this error:
Objects are not valid as a React child (found: [object HTMLDocument]).
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 Result
the code:
import React, { Component } from 'react';
import Result from './Result';
import * as $ from 'jquery';
import ReactFM from '../lib/ReactFM';
import { config } from '../config';
export let reactfm = new ReactFM(config.apiKey);
let name = $(() => {
$('.input-search').keypress(() => {
let inp = $('.input-search').val();
return reactfm.searchArtists(inp);
});
});
class SearchResults extends Component {
render() {
return (
<div className="search-results">
<Result avatar="" name={name} desc="um deus" />
</div>
);
}
}
export default SearchResults;
There really is no good reason to use jQuery when you are using React.
If you are (and you should be) writing the html that contains the input element in JSX as a higher level React component, you can reference the input either by a ref attribute or add a keydown event listener to the input itself.
Example:
const HigherLevelParentComponent = React.createClass({
getDefaultState() {
searchQuery: ''
},
searchArtists(event) {
this.setState({
searchQuery: event.target.value
})
},
render() {
(
<div>
<input type="text" onKeyDown={(event) => this.searchArtists(event)}/>
<Result ... /> // You probably want this to be a mapped array of Result components with unique props instead
</div>
)
}
})
You will also want to include a function that searches through the searchable data and returns an array of objects that you could then render into Result components using mapping and props. How you do this will depend on where the searchable data is coming from (React store, database table on the back-end, etc.).
Also, you mentioned using jQuery for animations but did not elaborate.
Related
I have a Table component that I want ref to be attached to.
Use: Table.js
class Table extends React.Component {
constructor(props) {
super(props);
this.state = {
rows: 1,
dataLength: props.dataLength,
}
this.tableRef = React.createRef();
}
componentDidUpdate() {
//using ref
this.tableRef.current ..... //logic using ref
this.state.rows ..... //some logic
}
render() {
<TableContainer ref={this.tableRef} />
<CustomPagination />
}
}
This works fine, but now my requirement has changed, and I want to reuse the Table component with pagination applied to all the Tables in my App. I have decided to make a HOC withCustomPagination.
Use: withCustomPagination.js HOC
import CustomPagination from 'path/to/file';
const withCustomPagination = tableRef => Component => {
return class WithCustomPagination extends React.Component {
constructor(props) {
super(props);
this.state = {
rows: 1,
dataLength: props.dataLength,
}
}
componentDidUpdate() {
tableRef.current.state ..... //logic using ref, Error for this line
this.state.rows ..... //some logic
}
render() {
return (
<Component {...state} />
<CustomPagination />
)
}
}
}
export default withCustomPagination;
New Table.js:
import withCustomPagination from '/path/to/file';
const ref = React.createRef();
const Table = props => (
<TableContainer ref={ref} />
);
const WrappedTable = withCustomPagination(ref)(Table);
HOC withCustomPagination returns a class WithCustomPagination that has a componentDidUpdate lifecycle method that uses Table ref in the logic. So I try to pass ref created in Table.js as argument to withCustomPagination, i.e curried with ref and Table stateless component.
This use of ref is wrong and I get error: TypeError: Cannot read property 'state' of null.
I tried using Forwarding Refs, but was unable to implement it.
How do I pass the Table ref to withCustomPagination and be able to use it in HOC?
In this case you can use useImperativeHandle
It means you have to forward ref and specify which function or object or,...
you want to share with ref inside your functional component.
Here is my Hoc example :
import React from 'react';
import { View } from 'react-native';
export function CommonHoc(WrappedComponent) {
const component = class extends React.Component {
componentDidMount() {
this.refs.myComponent.showAlert();
}
render() {
return (
<>
<WrappedComponent
ref='myComponent'
{...this.state}
{...this.props}
/>
</>
);
}
};
return component;
}
and it's my stateless component
const HomeController=(props,ref)=> {
useImperativeHandle(ref, () => ({
showAlert() {
alert("called");
},
}));
return (
<Text>home</Text>
);
};
export default CommonHoc(forwardRef(HomeController));
Either restructure your code to not use a HOC for this or try using React.forwardRef:
Refs Aren’t Passed Through
While the convention for higher-order components is to pass through
all props to the wrapped component, this does not work for refs.
That’s because ref is not really a prop — like key, it’s handled
specially by React. If you add a ref to an element whose component is
the result of a HOC, the ref refers to an instance of the outermost
container component, not the wrapped component.
The solution for this problem is to use the React.forwardRef API
(introduced with React 16.3). Learn more about it in the forwarding
refs section.
via Higher-Order Components: Refs Aren’t Passed Through
In the forwarding refs section there are code examples you could use to pass refs down, but trying to yank them up will fail in your case with:
Warning: Stateless function components cannot be given refs. Attempts to access this ref will fail.
In a project we took a different approach. There's an EnhancedTable component that handles all of the pagination logic and in itself has the dumb table component and the pagination component. It works pretty well but this means you would have to drill props (or use a store lib like Redux or Mobx) and add new ones that will handle pagination options. This will result in some refactoring of Table uses and you'll have to be more explicit but I would take it as a boon rather than a hindrance.
I was able to solve a simmilar issue that brought me to this thread without using forwardRef or useImperativeHandle.
By creating the ref at a higher level, and passign it down into the component and sub components that I needed to act on with the ref.
/** Parent Component has access to ref and functions that act on ref **/
import { useRef } from 'react';
const formRef = useRef(); // ref will have dom elements need accessing
const onClickFunction=()=>{ //sample function acts on ref
var inputs = formRef.current.querySelectorAll('input')
/* Act on ref here via onClick function, etc has access to dom elements
in child component and childs child components */
};
return(
<ComponentGetsAttachedRef formRef={formRef} />
//^ref sent down to component and its children
<ComponentNeedingRef onClickFunction={onClickFunction}/>
//^function with access to ref sent down to component
)
/** Child component needs to act on ref**/
export const ComponentNeedingRef = ({ onClickFunction}) =>{
return(
<button onClick={onClickFunction}>
)
}
/* Child component recieves ref and passes it down */
export const ComponentGetsAttachedRef = ({ formRef}) =>{
//ref comes in as prop gets attached to props or utilized internally
return (
<ChildsChildComponent formRef={formRef}/> //sub component passed ref down
)
}
In reactjs how do I dynamically update the QR code component when props change. I'm using this npm package: https://www.npmjs.com/package/kjua
componentWillMount() function is working on first load but it doesn't get updated but if I render {this.state.el} then I would get: *react-dom.development.js:57 Uncaught Error: Objects are not valid as a React child (found: [object HTMLImageElement]). If you meant to render a collection of children, use an array instead.*
```
import React, { Component } from 'react';
import kjua from "kjua";
class QrCodeOnAmountEntered extends Component {
constructor(props) {
super(props);
this.state = {
el: kjua({
text: `amount=${
this.props.text
}&memo=xxxx`
}),
amount: this.props.text
};
}
componentWillMount(){
document.querySelector('body').appendChild(this.state.el);
}
render() {
return (
<React.Fragment>
QrCodeOnAmountEntered amount: {this.props.text}
<p>Print QRCode here:</p>{this.state.el}
</React.Fragment>
);
}
}
export default QrCodeOnAmountEntered; ```
I need to be able to dynamically update the QRCode when props amount value changed.
Saving props to state is a react anti-pattern (in the case of props.text => this.state.amount), and use of componentWillMount isn't recommended, but instead use componentDidMount to issue side-effects after the component mounts.
I tried looking at the docs for kjua but it seems pretty dated. I assume you pass it some 'text' and it returns an QR code image of that text. HTMLImageElement appears to not be renderable in react as an object, but perhaps ,src will allow you to render it into an <img> tag.
render() {
const { el } = this.state;
return (
<React.Fragment>
QrCodeOnAmountEntered amount: {this.props.text}
<p>Print QRCode here:</p>
{el && <img src={el.src}/>}
</React.Fragment>
);
}
Assuming calls to kjua with text data returns a new image, then you want to use componentDidUpdate to check that new props have arrived, and if so, get new QR image and save to state:
componentDidUpdate(prevState, prevProps) {
// if current text value is not equal to the previous, calculate
// and save new QR code image in el, else ignore
if (prevProps.text !== this.props.text) {
const newQRcode = kjua({ text: `amount=${this.props.text}&memo=xxxx` });
this.setState({ el: newQRcode });
}
}
I want to use recompose utility functions as react element, so that I can use them in JSX as hoc.
const enhancedInput = props => {
return (<OnlyUpdateForKeys keys={['name']}>
<Input
id="name"
value={props.name}
onBlur={props.handleNameBlur}
onChange={props.updateName}
type="text"
className="validate"
placeholder="Enter Component Name"
/>
</OnlyUpdateForKeys>)
}
This is what I have tried till now, but it utterly fails.
import { onlyUpdateForKeys } from 'recompose';
export const OnlyUpdateForKeys = ({ keys, children }) => {
return onlyUpdateForKeys([...keys])(children)(children.props);
};
export default OnlyUpdateForKeys;
because children is a symbol and react-element instance and not a class/function.
react.development.js:368 Uncaught TypeError: Cannot set property 'props' of undefined
at Component (react.development.js:368)
at ShouldUpdate (Recompose.esm.js:558)
at OnlyUpdateForKeys (recomposeComponent.js:4)
at mountIndeterminateComponent (react-dom.development.js:14324)
Can somebody guide me?
onlyUpdateForKeys([...keys]) higher-order component expects React component as an argument, while children is React element:
onlyUpdateForKeys([...keys])(children)
It would be workable like:
export const OnlyUpdateForKeys = ({ keys, children }) => {
const EnhancedChild = onlyUpdateForKeys([...keys])(props => children);
return <EnhancedChild/>;
};
But it doesn't make sense because it doesn't prevent child component from being updated. OnlyUpdateForKeys is created on each EnhancedInput render. Input is rendered every time EnhancedInput is rendered too, because children are rendered any way - otherwise they wouldn't be available as props.children.
While onlyUpdateForKeys is supposed to be used as:
const EnhancedInput = onlyUpdateForKeys(['name'])(props => (
<Input ... />
))
It's shorter and more efficient than OnlyUpdateForKeys utility component.
This should be super simple for some of you. I have a super simple app that I am making to teach myself the glory that is React and reactDom. Currently, I am pulling from an API (which actually works!), however, I am unable to see any data when rendering to the screen. Literally just two components. I am sure that I am using props or state wrong here, I just don't know where. It's possible that my map function is the problem as well.
Here is the code:
Parent:
import React from "react";
import ReactDOM from "react-dom";
import axios from 'axios'
import { Table } from './Table'
export class DataList extends React.Component {
state = {
articles: []
}
componentDidMount() {
axios.get('http://127.0.0.1:8000/api/portblog/')
.then(res => {
this.setState({
articles: res.data
})
console.log(res.data)
})
}
render() {
return(
<div>
<Table id={this.state.articles.id} articles={this.state.articles} />
</div>
)
}
}
export default DataList
And the child:
import React from "react";
import PropTypes from "prop-types";
import key from "weak-key";
export const Table = (props) => (
<div>
<h1>Welcome to the Article List Page Home</h1>
<li>{props.articles.map((article) => {
{article.titles}
})}</li>
</div>
);
export default Table;
The problem is that your map() call is not returning anything. You need to do something like:
<div>
<h1>Welcome to the Article List Page Home</h1>
{props.articles.map(article =>
<li>{article.titles}</li>
)}
</div>
I'm not exactly sure what your desired output is, but generally you map over data to generate a set of dom elements.
The problem is
<li>{props.articles.map((article) => {
{article.titles}
})}</li>
JSX expressions cannot be used in any arbitrary place. props.articles.map(...) is already an expression, so creating a new one wouldn't make sense.
{article.titles} inside a function creates a block that does nothing. Nothing is returned from map callback, the array isn't mapped to anything.
Depending on what should resulting layout look like, it should be either
<li>{props.articles.map((article) => article.titles)}</li>
output titles within single <li> tag, or
{props.articles.map((article) => <li>{article.titles}</li>)}
to output a list of <li> tags.
ESLint array-callback-return rule can be used to prevent the problem with callback return value.
So, i’d like to spare time later and want to do a dynamically generated page. For that reason i want to read component data from an object, like this:
layout: {
toolbar: {
components: [
{
type: "Testcomp",
theme: "default",
data: "div1"
},
{
type: "Testcomp",
theme: "pro",
data: "div2"
},
]}}
The component would be dynamically imported, enabled/activated and besides that this is the jsx code supposed to render components dynamically:
render() {
const toolbarComponents = userSession.layout.toolbar.components.map(Component => (
<Component.type theme={Component.theme} data={Component.data} key={this.getKey()} />
));
return (
<div>
<div className="toolbar">
toolbar
{toolbarComponents}
</div>
. . .
</div>
);
}
However i get the following warning in Chrome’s devtool, also the component is not displayed:
Warning: is using incorrect casing. Use PascalCase for React components, or lowercase for HTML elements.
Warning: The tag is unrecognized in this browser. If you meant to render a React component, start its name with an uppercase letter.
What’s wrong with my code?
You are getting those errors because you are not referencing the component itself here, instead using a string as name. So, maybe you need to think another way to create the components dynamically. Like starting with a base component and only give some props and data to it.
// Define above main component or elsewhere then import.
const MyComponent = ( props ) => <div>{props.data}</div>
// Main component
render() {
const toolbarComponents = userSession.layout.toolbar.components.map(
component => <MyComponent theme={component.theme} data={component.data} />
);
return <div className="App">{toolbarComponents}</div>;
}
Here we are not using a type key anymore. If you want to use different components like that, you can create every base component and then use as its name with type key but not with string, directly referencing the component.
I tried using the same method as you, wherein I was using a string to reference a React component by name. However, it doesn't appear designed to be used outside of standard tags like div.
Instead, what worked for me is to import the component I wanted to show and then set that in the state.
import MyComponent from 'mycomponent';
class Parent extends React.Component {
constructor() {
super();
this.state = {
selectedComponent: null
};
}
render() {
return (
<div className="Parent">
<h2>Parent Component</h2>
<button onClick={() => this.setState({ selectedComponent: MyComponent })}>Show my component</button>
{this.state.selectedComponent !== null && React.createElement(this.state.selectedComponent, null, null)}
</div>
);
}
}