Add placeholder div after third element in array in React app - javascript

In my React app I am rendering some blocks:
const MyBlocks = ({ id }: { id: string }) => {
const { data, loading } = useQuery<GqlRes>(BlocksQuery, {
ssr: false,
errorPolicy: 'all',
variables: {
blocksId: parseInt(id, 10),
},
});
if (loading) {
return <CircularProgress />;
}
return (
<React.Fragment>
{data?.blocks.map((item, i) => (
<Block key={String(i)} data={item} />
))}
</React.Fragment>
);
};
export default MyBlocks;
When there are more than 3 blocks rendered by the backend, I want to add a placeholder <div> (filled by a third party script) after the third block. So I get:
<Block>
<Block>
<Block>
<div id="placeholder" />
<Block>
<Block>
How do I do that, what's a nice solution for this?

<React.Fragment>
{data?.blocks.map((item, i) => (
<>
<Block key={String(i)} data={item} />
{ i === 2 && <div id="placeholder" /> }
</>
))}
</React.Fragment>

Related

Passing HTML element as a prop with other props

I have a component where I need to pass a HTML element as a prop to another element
const MyText = () => {
return (
<>
<h1>Sample heading</h1>
</>
)
}
return (
<div>
<MyComponent Text={MyText} onClose={() => setShow(false)} show={show} />
</div>
);
MyComponent.js
export default function MyComponent(props) {
return (
<>
{props.Text}
</>
);
}
Issue: I'm not getting anything rendered on the screen. Am I missing something here?
There are two ways.
Option 1: Passing a component type (or class if you are coming from OOP background)
const MyText = () => {
return (
<>
<h1>Sample heading</h1>
</>
)
}
return (
<div>
<MyComponent Text={MyText} onClose={() => setShow(false)} show={show} />
</div>
);
const MyComponent = ({ Text }) => {
return (
<>
<Text />
</>
);
}
Option 2: Passing a component (or instance if you are coming from OOP background)
const MyText = () => {
return (
<>
<h1>Sample heading</h1>
</>
)
}
return (
<div>
<MyComponent text={<MyText />} onClose={() => setShow(false)} show={show} />
</div>
);
const MyComponent = ({ text }) => {
return (
<>
{text}
</>
);
}

How to render a list of components held as a key value

I have the following list of React components and can't change this format.
How could I render this list on my page by looping over it in some way?
const allComponents = isValid => [
{
Component: (
<ComponentA
isTransparent={true}
/>
),
},
{
Component: (
<div>
{<ComponentB/>}
</div>
),
},
!isValid && {
Component: (
<div>
{<ComponentC/>}
</div>
),
},
].filter(Boolean);
Within my return block tried the following:
return (
<Fragment>
{allComponents(false).map(c => (
{c}
))}
</Fragment>
);
End up with following error.
Error! Objects are not valid as a React child.
(found: object with keys {c}). If you meant to render a
collection of children, use an array instead.
But the above allComponents is an array.
Could I please get some advice on this pls.
The JSX stored in the the array returned by allComponents() needs to be returned from a valid function component. You can either turn the Component properties into functions
{
Component: () => (
<ComponentA />
),
},
// And then call it in the map()
{allComponents(false).map(c => (
c.Component()
))}
or return the JSX from an IIFE inside the map() call
{allComponents(false).map(c => (
(() => c.Component)()
))}
Working snippet
const App = () => {
const allComponents = isValid => [
{
Component: (
<ComponentA />
)
,
},
{
Component: (
<div>
{<ComponentB />}
</div>
)
,
},
!isValid && {
Component: (
<div>
{<ComponentC />}
</div>)
,
},
].filter(Boolean);
return (
<div>
<p>isValid: False</p>
<div>
{allComponents(false).map(c => (
(() => c.Component)()
))}
</div>
<p>isValid: True</p>
<div>
{allComponents(true).map(c => (
(() => c.Component)()
))}
</div>
</div>
);
}
const ComponentA = () => {
return (
<div>Component A</div>
)
}
const ComponentB = () => {
return (
<div>Component B</div>
)
}
const ComponentC = () => {
return (
<div>Component C</div>
)
}
ReactDOM.render(
<App />,
document.getElementById("root")
);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.4/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.4/umd/react-dom.production.min.js"></script>
<div id="root"></div>
return (
<Fragment>
{allComponents(false).map(c => (
{c.component}
))}
</Fragment>
);
you are attempting to render an object in your above example and not the component itself. IMO I would update your overall structure

× TypeError: Cannot read property 'map' of undefined. found error

Can anyone pls help me out with this problem?
const Users = ({ users, loading }) => {
if (loading) {
return <Spinner />
} else {
return (
<div style={userStyle}>
{users.map((user) => (
<UserItem key={users.id} user={user} />
))}
</div>
)
}
}
use conditional operator && on before map
const Users = ({ users, loading }) => {
if (loading) {
return <Spinner />
} else {
return (
<div style={userStyle}>
{users && users.map((user) => (
<UserItem key={user.id} user={user} />
))}
</div>
)
}
}
Inside the map, you should use user not users so the code will be
{ users.map((user) => (
<UserItem key={user.id} user={user} />
))}
Because, you are passing user as a argument for callback function not users.

Add additional props to JSX Element variable

How can I add additional props to a JSX.Element variable that is passed as a prop?
First I create the variable like so
const leftIcon = <SmallIcon icon="note" color={colors.red} />
Then it is passed to my function component and used like
const ScreenHeader: React.FunctionComponent<ScreenHeaderProps> = ({
leftIcon = <></>,
}) => {
return (
<View>
<Header
leftComponent={leftIcon}
/>
</View>
)};
How can I add an additional styles prop to the "leftIcon" variable before it is used in Header?
If you initialize a variable with a React component the way you're doing it right now (const leftIcon = <SmallIcon />), then you won't be able to pass additional props into it.
Here's a possible solution:
// make `LeftIcon` into a function so that you
// can use it in the following way: `<LeftIcon />`
const LeftIcon = (props) => (
<div className="LeftIcon" onClick={() => {}} {...props}>
<p>I am a left icon!</p>
<p>Additional props: {JSON.stringify(props)}</p>
</div>
);
const ScreenHeader = ({ leftComponent = null }) => {
const CustomLeftComponent = leftComponent ? leftComponent : null;
const greenComponent = CustomLeftComponent
? <CustomLeftComponent style={{ color: "green" }} />
: null;
return (
<div>
<p>I am a screen header!</p>
{greenComponent}
</div>
);
};
function App() {
return (
<div className="App">
<ScreenHeader leftComponent={LeftIcon} />
<hr />
<ScreenHeader />
</div>
);
}
ReactDOM.render(
<App />,
document.getElementById("app")
)
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
<div id="app"></div>
Alternatively, you could pass additional props to the LeftIcon component prior to using it inside ScreenHeader:
// make `LeftIcon` into a function so that you
// can use it in the following way: `<LeftIcon />`
const LeftIcon = (props) => (
<div className="LeftIcon" onClick={() => {}} {...props}>
<p>I am a left icon!</p>
<p>Additional props: {JSON.stringify(props)}</p>
</div>
);
const ScreenHeader = ({ leftComponent = null }) => {
return (
<div>
<p>I am a screen header!</p>
{leftComponent}
</div>
);
};
function App() {
return (
<div className="App">
<ScreenHeader leftComponent={<LeftIcon style={{ color: "green" }} />} />
<hr />
<ScreenHeader />
</div>
);
}
ReactDOM.render(
<App />,
document.getElementById("app")
)
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
<div id="app"></div>
You can use React.cloneElement to add additional props
const ScreenHeader: React.FunctionComponent<ScreenHeaderProps> = ({
leftIcon = <></>,
}) => {
return (
<View>
<Header
leftComponent={React.cloneElement(leftIcon, {className: classes.myClass})}
/>
</View>
)};

Changing className in react based on button click while avoiding more if / elses

Got a small project that I've been working on for past couple weeks. Basic premise is that there is a stepper with steps. User can click on each of them and get a description of what are his to-do's of each step.
There is also a button to complete the step. At the moment I am stuck on the part with making the button work properly.
What I want: When user clicks button to complete step, that stepper item changes its styling (This case classname) to appear as if its complete, and the button get disabled for next step.
I'm now at the point with many if/elses that I no longer have any clue how to finish this task and even if I was to rewrite the if's and else's in a different way I have no clue how as I am pretty new to react.
Some code snippets:
My parent component.
class UserPage extends Component {
state = {
currentStep: 1,
pendingApproval: false
}
clickHandler(e, step) {
e.preventDefault();
this.setState({ currentStep: step })
}
incrimentStep(e, step) {
e.preventDefault();
this.setState({ currentStep: step, pendingApproval: true })
}
render() {
return (
<Grid>
<Grid.Row columns={1}>
<Grid.Column stretched>
<Stepper
clickHandler={this.clickHandler.bind(this)}
steps={this.props.user.process.steps}
pendingApproval={this.state.pendingApproval}
/>
</Grid.Column>
</Grid.Row>
<Grid.Row columns={1}>
<Grid.Column stretched>
<Steps currentStep={this.state.currentStep} steps={this.props.user.process.steps} />
<StepButton
incrimentStep={this.incrimentStep.bind(this)}
currentStep={this.state.currentStep}
steps={this.props.user.process.steps}
pendingApproval={this.state.pendingApproval}
/>
</Grid.Column>
</Grid.Row>
</Grid>
)
}
}
export default UserPage;
My child component that I am trying to fix:
class UserStepper extends Component {
render() {
const steps_length = this.props.steps.length;
let handler = this.props.clickHandler;
// let incompleteStepsArray = []
// let incompleteID = -1;
let pendingApproval = this.props.pendingApproval;
return (
<div id="stepper-container">
{this.props.steps.map(function (step, index) {
if (step.isDone) {
if (index !== steps_length - 1) {
return (
<div key={index} className="completed">
<StepperFill
index={index + 1}
click={handler}
steps={step[index]}
class_name="completecontainer"
/>
<CircleCompleted />
</div>
)
}
else {
return (
<div key={index} className="completed">
<StepperFill
index={index + 1}
click={handler}
steps={step[index]}
class_name="completecontainer"
/>
</div>
)
}
}
else {
{/* incompleteID = incompleteID +1; */}
{/* console.log(incompleteStepsArray) */}
if (index !== steps_length - 1) {
return (
<div key={index} className="incompleted">
<StepperFill
index={index + 1}
click={handler}
steps={step[index]}
class_name="incompletecontainer"
/>
<CircleIncompleted />
</div>
)
}
else {
return (
<div key={index} className="incompleted">
<StepperFill
index={index + 1}
click={handler}
steps={step[index]}
class_name="incompletecontainer"/>
</div>
)
}
}
})}
</div>
)
}
}
//Functional component to decide wether the stepper should be filled or left
blank (Complete or Incomplete)
const StepperFill = (props) => {
return (<div onClick={(e) => props.click(e, props.index)} className=
{props.class_name}>
<p>Step {props.index}</p>
</div>
)
}
const CircleCompleted = () => {
return (
<div className='circle_completed'>
<Icon.Group size='big'>
<Icon size='small' name='checkmark' color='green' />
</Icon.Group>
</div>
)
}
const CircleIncompleted = () => {
return (
<div className='circle_incompleted'>
</div>
)
}
export default UserStepper;
Sorry for the long code, no other idea how to show it otherwise that it would make sense as to what is happening. Any suggestions are appreciated.
Theres many ways you can refactor this but to disable the button a simple approach would be to add a isNextBtnDisabled to your state and pass it down to the button. Whenever you want your button disabled simple set that variable to true.
class UserPage extends Component {
state = {
currentStep: 1,
pendingApproval: false,
isNextBtnDisabled: false,
}
clickHandler(e, step) {
e.preventDefault();
this.setState({ currentStep: step, isNextBtnDisabled: this.state.pendingApproval })
}
incrimentStep(e, step) {
e.preventDefault();
this.setState({ currentStep: step, pendingApproval: true, isNextBtnDisabled: true })
}
render() {
return (
<Grid>
<Grid.Row columns={1}>
<Grid.Column stretched>
<Stepper
clickHandler={this.clickHandler.bind(this)}
steps={this.props.user.process.steps}
pendingApproval={this.state.pendingApproval}
/>
</Grid.Column>
</Grid.Row>
<Grid.Row columns={1}>
<Grid.Column stretched>
<Steps currentStep={this.state.currentStep} steps={this.props.user.process.steps} />
<StepButton
incrimentStep={this.incrimentStep.bind(this)}
currentStep={this.state.currentStep}
steps={this.props.user.process.steps}
pendingApproval={this.state.pendingApproval}
disabled={this.state.isNextBtnDisabled}
/>
</Grid.Column>
</Grid.Row>
</Grid>

Categories