I am trying to create very simple multistep form using react. My main component which is handling state for steps looks like this:
...
renderStepItem = () => {
switch(this.state.step) {
case 1:
return <ImportStep nextStep={this.nextStep} />
case 2:
return <ImportStep previousStep={this.previousStep} nextStep={this.nextStep} />
case 3:
return <ImportStep previousStep={this.previousStep} />
}
}
...
This is switch which should return Component that should be rendered based on step state
Then render:
render() {
return(
{this.renderStepItem()}
)
}
The problem is that i am getting following error message:
Error
Objects are not valid as a React child (found: object with keys {nextStep}).
I was trying to go through some tuts etc to solve it. But i am guessing that i am passing something that i am unable to do.
Can anybody give me some hint please?
UPDATE:
render() {
const importSteps = AppConfig.importSteps;
return (
<Block extend={{
width: '80%',
margin: '25px auto'
}}>
<TabNav extend={{
border: '1px solid black',
}}
textAlign='center'>
{Object.keys(importSteps).map(function(key) {
return <TabNavItem >{importSteps[key].name} {importSteps[key].stepNo}</TabNavItem>;
}
)}
</TabNav>
<div>{ this.renderStepItem() }</div>
</Block>
)
}
}
UPDATE2: ImportItem component
import React, { Component } from 'react';
import { Block } from 'vcc-ui';
class ImportStep extends Component {
render() {
return (
<Block>
<h3>{this.props}</h3>
</Block>
)
}
}
export default ImportStep;
UPDATE
Use this.props.property in the render function. You can not use an object there in the ImportStep component.
Also, wrapping inside a <div> would be necessary when you have only one statement inside the return.
Wrap your this.renderStepItem() inside a <div></div>, and that should do.
Here is what your return statement should look like,
return (
<div>{ this.renderStepItem() }</div>
)
See this example: https://codesandbox.io/s/q35699jj49
Related
I have a small part of my new React app which contains a block of text, AllLines, split into line-by-line components called Line. I want to make it work so that when one line is clicked, it will be selected and editable and all other lines will appear as <p> elements. How can I best manage the state here such that only one of the lines is selected at any given time? The part I am struggling with is determining which Line element has been clicked in a way that the parent can change its state.
I know ways that I can make this work, but I'm relatively new to React and trying to get my head into 'thinking in React' by doing things properly so I'm keen to find out what is the best practice in this situation.
class AllLines extends Component {
state = {
selectedLine: 0,
lines: []
};
handleClick = (e) => {
console.log("click");
};
render() {
return (
<Container>
{
this.state.lines.map((subtitle, index) => {
if (index === this.state.selectedLine) {
return (
<div id={"text-line-" + index}>
<TranscriptionLine
lineContent={subtitle.text}
selected={true}
/>
</div>
)
}
return (
<div id={"text-line-" + index}>
<Line
lineContent={subtitle.text}
handleClick={this.handleClick}
/>
</div>
)
})
}
</Container>
);
}
}
class Line extends Component {
render() {
if (this.props.selected === true) {
return (
<input type="text" value={this.props.lineContent} />
)
}
return (
<p id={} onClick={this.props.handleClick}>{this.props.lineContent}</p>
);
}
}
In your case, there is no really simpler way. State of current selected Line is "above" line collection (parent), which is correct (for case where siblings need to know).
However, you could simplify your code a lot:
<Container>
{this.state.lines.map((subtitle, index) => (
<div id={"text-line-" + index}>
<Line
handleClick={this.handleClick}
lineContent={subtitle.text}
selected={index === this.state.selectedLine}
/>
</div>
))}
</Container>
and for Line component, it is good practice to use functional component, since it is stateless and even doesn't use any lifecycle method.
Edit: Added missing close bracket
'Thinking in React' you would want to give up your habit to grab DOM elements by their unique id ;)
From what I see, there're few parts missing from your codebase:
smart click handler that will keep only one line selected at a time
edit line handler that will stick to the callback that will modify line contents within parent state
preferably two separate components for the line capable of editing and line being actually edited as those behave in a different way and appear as different DOM elements
To wrap up the above, I'd slightly rephrase your code into the following:
const { Component } = React,
{ render } = ReactDOM
const linesData = Array.from(
{length:10},
(_,i) => `There goes the line number ${i}`
)
class Line extends Component {
render(){
return (
<p onClick={this.props.onSelect}>{this.props.lineContent}</p>
)
}
}
class TranscriptionLine extends Component {
constructor(props){
super(props)
this.state = {
content: this.props.lineContent
}
this.onEdit = this.onEdit.bind(this)
}
onEdit(value){
this.setState({content:value})
this.props.pushEditUp(value, this.props.lineIndex)
}
render(){
return (
<input
style={{width:200}}
value={this.state.content}
onChange={({target:{value}}) => this.onEdit(value)}
/>
)
}
}
class AllLines extends Component {
constructor (props) {
super(props)
this.state = {
selectedLine: null,
lines: this.props.lines
}
this.handleSelect = this.handleSelect.bind(this)
this.handleEdit = this.handleEdit.bind(this)
}
handleSelect(idx){
this.setState({selectedLine:idx})
}
handleEdit(newLineValue, lineIdx){
const linesShallowCopy = [...this.state.lines]
linesShallowCopy.splice(lineIdx,1,newLineValue)
this.setState({
lines: linesShallowCopy
})
}
render() {
return (
<div>
{
this.state.lines.map((text, index) => {
if(index === this.state.selectedLine) {
return (
<TranscriptionLine
lineContent={text}
lineIndex={index}
pushEditUp={this.handleEdit}
/>
)
}
else
return (
<Line
lineContent={text}
lineIndex={index}
onSelect={() => this.handleSelect(index)}
/>
)
})
}
</div>
)
}
}
render (
<AllLines lines={linesData} />,
document.getElementById('root')
)
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.12.0/umd/react.production.min.js"></script><script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.11.0/umd/react-dom.production.min.js"></script><div id="root"></div>
I want to create an array of array in reactjs and render it. Currently,
I have an array and and I push one component to it and it gets rendered on the screen. However what i want is that after making my second selection, both first and second component should be seen on screen .
renderQuestions=()=>{
let questions=[];
console.log('rating',this.state.ratingType);
if(this.state.ratingType=== '1'){
questions.push(<NumberRating/>);
}
else if (this.state.ratingType === '2' ){
questions.push( <StarRating/>);
}
else if (this.state.ratingType === '3'){
questions.push( <PollRating/>);
}
console.log('Questions',questions);
console.log('Length',questions.length);
return questions;
}
and inside render I have
render(){
return(
<RaisedTextButton
titleColor="white"
title={'Add Question'}
onPress={() => this.openDialog()}
/>
<FormDialog
title={'Select the kind of rating'}
visible={this.state.visible}
onRequestClose={()=>this.submitDialog()}
onSubmit={()=>this.hideDialog()}
>
<Select
label='Label '
options={[
{_id: '1', name: 'NumberRating'},
{_id: '2', name: 'StarRating'},
{_id:'3', name: 'PollRating'}
]}
labelKey="name"
onChange={(value) =>this.questionType(value)}
valueKey="_id"
isValueObject={!false}
/>
</FormDialog>
{this.renderQuestions()}
</View>
);}
Please note that I have a button in my render and on pressing this ,the state.ratingType changes .
return (
<React.Fragment>
{this.renderQuestions().map(question => question)}
</React.Fragment>
)
Here, this.renderQuestions() returns array. This needs to be wrapped with single node (Either div or if you dont have to use div, you can use React.Fragment)
Change your return code with this code. Actually when you call the method id return of render() it should return jsx or some text. But array is js variable and it will not we itself splitted in to parts to be render(). You need to but each of the component of the array in a container and then return it
return (
<div className="container">
{questions.map(question => (question))}
</div>
)
You can have an array in the State of the component, which holds the ratingType selection on click of the button and in the render() based on the array render the components.
Constructor(){
super();
this.state={
questions:[]
}
}
A thought; your if-else-if branches can only ever allow you to render one of your ratingTypes. Is this intended? This has nothing to do with rendering lists; just JavaScript.
Two options for rendering lists:
import React, { Component } from 'react'
class App extends Component {
renderQuestions() {
return [
'a', 'b', 'c'
]
}
renderQuestionElements() {
return [
(<div>1</div>),
(<div>2</div>),
(<div>3</div>)
]
}
render() {
const questions = this.renderQuestions()
return (
<React.Fragment>
{questions.map((value, index) => (<div key={index}>{ value }</div>))}
{this.renderQuestionElements()}
</React.Fragment>
)
}
}
export default App;
I'm making a Calendar which consists of an header and calendar. The header is for picking the type of calendar; weekly or monthly.
I had to make a dummy component called CalendarPicker just so I can use a switch. Inline switch is what I think needed but jsx doesn't accept it.
Is there a better way to do this? Or another way to match strings to components?
<CalendarController
render={({ type, onTypeClick }) => (
<>
<header>
<p>header of agenda</p>
<button onClick={onTypeClick("weekly")}>weekly</button>
<button onClick={onTypeClick("monthly")}>monthly</button>
</header>
<CalendarPicker
type={type}
render={type => {
switch (this.props.type) {
case "monthly":
return <MonthlyCalendar />;
case "weekly":
return <WeeklyCalendar />;
default:
return <MonthlyCalendar />;
}
}}
/>
</>
)}
/>
This is what I did to achieve this. In my case there were around 50 components and according to the name in the string I had to render that component.
I created a file ComponentSelector.js which imports all the components.
ComponentSelector.js
export default const objComponents = {
MonthlyCalendar : {
LoadComponent: function () {
return require('../../Modules/MonthlyCalendar ').default;
}
},
WeeklyCalendar : {
LoadComponent: function () {
return require('../../Modules/WeeklyCalendar ').default;
}
}
}
Import it into your CalendarController component
import objComponents from './ComponentSelector.js';
render(){
var CalComp=objComponents[this.props.type].LoadComponent();
return(<div>
<header>
<p>header of agenda</p>
<button onClick={onTypeClick("weekly")}>weekly</button>
<button onClick={onTypeClick("monthly")}>monthly</button>
</header>
<CalComp type={type}/>
</div>)
}
I am new in react.
I try to output two components with react 16+, that starting like this:
function InsuranceInfo(props) {...
// and
function InsuranceCustomerInfo(props) {...
and main component render function look like this
render()
{
return (
<InsuranceInfo args={this.state.orderIfo}/>,
<InsuranceCustomerInfo args={this.state.orderIfo}>
)
}
when i load the page i see only last one.
can any one help please? thank you!
Do not use comma (,) sign between component. Either wrap the returning component in some html element
render()
{
return (
<div>
<InsuranceInfo args={this.state.orderIfo}/>
<InsuranceCustomerInfo args={this.state.orderIfo} />
</div>
)
}
or use React Fragments:
render()
{
return (
<React.Fragment>
<InsuranceInfo args={this.state.orderIfo}/>
<InsuranceCustomerInfo args={this.state.orderIfo} />
</React.Fragment>
)
}
Try this, which use Fragment
render()
{
return (
<>
<InsuranceInfo args={this.state.orderIfo}/>
<InsuranceCustomerInfo args={this.state.orderIfo}>
</>
)
}
Or array
render()
{
return [
<InsuranceInfo key="info" args={this.state.orderIfo}/>,
<InsuranceCustomerInfo key="customer" args={this.state.orderIfo}>
];
}
The proper way to achieve what you want is to use HOC (Higher-Order Components)
Have a look at the documentation here for more details.
I get an error that says this2.sampleFunction is not an object , I have tried adding a constructor and all from previous solutions.
export default class Play extends React.Component {
sampleFunction() {
console.log('Hello');
}
anotherFunction() {
return (
<Button
onPress={() => this.sampleFunction.bind(this)} />
);
}
render() {
<Deck
anotherFunction={this.anotherFunction()}
/>
}
}
EDIT : Here is the code in the deck component, it's just mainly a view tag
render() {
return (
<View>
{this.props.anotherFunction()}
</View>
);
}
Here is the image for the error :
I got the problem, It is same as I described in the comment. You are passing an element from Play to Deck but Deck expects a function. Please change render method of Play to
render() {
return (<Deck
anotherFunction={this.anotherFunction.bind(this)}
/>);
}