Elegant ways of handling conditional rendering of multiple components - javascript

Say I have a wizard-like view with an arbitrary number of steps:
const StepsComponent = () => {
const [stage, setStage] = useState(1);
const stageProps = {stage, setStage};
const stageMachine = () => {
switch (stage) {
case 1:
return <One {...stageProps} />;
case 2:
return <Two {...stageProps} />;
case 3:
return <Three {...stageProps} />;
default:
return <One {...stageProps} />;
}
};
return (
<>
{stageMachine()}
</>
);
}
Are there more elegant ways of handling such cases, other than switch statements or ternary expressions?
If I would have wizards with 10+ steps, then it'd be a real mess to manage it.
Probably I could do something like this but this seems hacky, doesn't it?
const stageMachine = Object.freeze({
1: <One {...stageProps} />,
2: <Two {...stageProps} />,
3: <Three {...stageProps} />
});
Also I don't like the idea of invoking stageMachine function in return, it is considered a bad practice?

You can try this
const StepsComponent = () => {
const [stage, setStage] = useState(1);
return (
<>
{stage === 1 && <One />}
{stage === 2 && <Thow />}
{stage === 3 && <Three />}
</>
);
}

Related

React: Cannot assign to read only property '_status' of object '#<Object>'

I've been struggling over this error for a while now. It happens when I try to open react-bootstrap Modal with dynamically passed lazy component referrence and props to render it inside. It worked with classic import.
First row points to some react's internal lazy handler:
This is how modals are handled inside my ModalProvider:
const ModalList = React.memo(({ modalList, closeModalByIndex, confirmModalExitByIndex }) =>
modalList.map((modalDef, index) => {
const closeModal = () => closeModalByIndex(index);
const onConfirmExitChange = (confirmExit) => confirmModalExitByIndex(index, confirmExit);
const props = { ...modalDef, key: index, closeModal, onConfirmExitChange };
switch (modalDef.type) {
case TYPE_LIST:
return (
<React.Suspense fallback={fallback}>
<ListModal {...props} />
</React.Suspense>
);
case TYPE_FORM:
return (
<React.Suspense fallback={fallback}>
<FormModal {...props} />
</React.Suspense>
);
case TYPE_LIST_MULTI:
return (
<React.Suspense fallback={fallback}>
<ListMultiModal {...props} />
</React.Suspense>
);
default:
return null;
}
})
);
And this is how it is passed:
const openListModal = (Component, componentProps) => openModal(Component, componentProps, TYPE_LIST);
Anyone with deeper understanding what could possibly cause this?
Found out by trial by error. It was caused by immer's produce function which builds read-only deep copy of object.
setModalList(
produce(modalList, (modalList) => {
modalList.push({ Component, componentProps, type, show: true });
})
);

how to render if else and else if statement in react functional component

const GharwapasiForm = () => {
const [activeStep,setActiveStep] = useState(0);
const steps = [
'Click Your Photo',
'Personal Details',
'Submit Application',
];
return (
<FormContainer className='row'>
<Stepper activeStep={0} className="py-2" >
{steps.map((label) => (
<Step key={label}>
<StepLabel>{label}</StepLabel>
</Step>
))}
</Stepper>
</FormContainer>
)
}
export default GharwapasiForm
Is active step one i want to render "step one"if step two i want to render "This is the step two" and similarly for the third step the problem is only know how to write if and else like {condition ? "render if":"render else"} what if i had to check more than one statement
I think the clean way (not to compose ternary operators) is to create a component.
Example:
const StepTitle = ({ step }} => {
if (step === 1) {
return <span>step one</span>
}
if (step === 2) {
return <span>This is the step two</span>
}
return <span>something</span>
}
that will be for something more complex than just text, for text you can just:
const texts = { "1": "step one", "2": "This is the step two" }
// in code
return texts[step] || "some default"
You can do something like this.when state change your component getting rendering, so that you can write if condition inside the react component to check whether your activeStep state change.See the below code.
const GharwapasiForm = () => {
const [activeStep,setActiveStep] = useState(0);
let activeStepString="";
if(activeStep==0)
{
activeStepString="step one";
}
else if(activeStep==2){
activeStepString="step two;
}
else if(activeStep==3){
activeStepString="step three;
}
else{
activeStepString="step four;
}
const steps = [
'Click Your Photo',
'Personal Details',
'Submit Application',
];
return (
<FormContainer className='row'>
<Stepper activeStep={0} className="py-2" >
{steps.map((label) => (
<Step key={label}>
<StepLabel>{activeStepString}</StepLabel>
</Step>
))}
</Stepper>
</FormContainer>
)
}
export default GharwapasiForm
You can write code more like JSX.
const [activeStep, setActiveStep] = useState(0);
const titles = [
<span className="one">one</span>,
<h2 className="two">two</h2>,
<div className="three">three</div>,
]
return (
<div>
{titles[activeStep]}
</div>
);

Pass same Props to multiple React component

I'm currently rendering two different components based on the value of shouldRenderPlanA - however, despite different components being rendered (depending on the value) - I pass both the same props. How can I condense this to reduce repeated code?
return (
<>
{options.map(option)}
<StyledRow>
{variousOptions.map((opt) => (
shouldRenderPlanA ? (
<StyledLabelOptionOne
variousProps={variousProps}
variousProps={variousProps}
variousProps={variousProps}
/>
) : (
<StyledLabelOptionTwo
variousProps={variousProps}
variousProps={variousProps}
variousProps={variousProps}
/>
)
))}
</StyledRow>
</>
);
To pass same Props to multiple React component or to pass multiple Props to a React component, you can use Object unpacking/destruction within components.
function Component() {
const propPack = {
variousProps1: variousProps1,
variousProps2: variousProps2,
variousProps3: variousProps3,
};
return (
<>
{options.map(option)}
<StyledRow>
{variousOptions.map((opt) => (
shouldRenderPlanA
? <StyledLabelOptionOne {...propPack} />
: <StyledLabelOptionTwo {...propPack} />
))}
</StyledRow>
</>
);
}
This is commonly used to pass all the parent props down to children
function Component(props) {
return (
condition
? <StyledLabelOptionOne {...props} />
: <StyledLabelOptionTwo {...props} />
)
}
You can also conditionally pick the component for rendering (but this IMHO is less readable)
function Component() {
const PickedComponent = shouldRenderPlanA ? StyledLabelOptionOne : StyledLabelOptionTwo;
return (
<>
{options.map(option)}
<StyledRow>
{variousOptions.map((opt) => (
<PickedComponent
variousProps1={variousProps1}
variousProps2={variousProps2}
variousProps3={variousProps3}
/>
))}
</StyledRow>
</>
);
}
For conditions/props derived from within .map() simply move the code within the map callback
function Component() {
return (
<>
{options.map(option)}
<StyledRow>
{variousOptions.map((opt) => {
const propPack = {
variousProps1: variousProps1,
variousProps2: opt.value,
};
const PickedComponent = opt.condition ? StyledLabelOptionOne : StyledLabelOptionTwo;
return (
shouldRenderPlanA
? <StyledLabelOptionOne {...propPack} />
: <StyledLabelOptionTwo {...propPack} />
)
})}
</StyledRow>
</>
);
}
Note how arrow function within map has becomed an arrow function with a complete block. From (opt) => (first_instruction) to (opt) => { first_instruction; return (second_instruction); }. This allows us to add code before rendering at each map() cycle.
You could assign both options to a variable which contains a union of both component types.
Combining and then spreading the props from an object may also be beneficial, depending on where those props come from. If they are taken from opt inside the map then this second step is probably not required:
const LabelComponent = shouldRenderPlanA ? StyledLabelOptionOne : StyledLabelOptionTwo;
return (
<>
{options.map(option)}
<StyledRow>
{variousOptions.map((opt) => (
<LabelComponent
prop1={opt.prop1}
prop2={opt.prop2}
/>
))}
</StyledRow>
</>
);
You could use the React Context API. This would enable you to share the props across multiple children without passing it to each one of them explicitly.

How to make React slick slider work with icons?

There is a code that displays icons
switch (name) {
case Header.Arrows.name:
return <ArrowsComponent key={name} color={color}/>;
case Header.Zoom.name:
return <ZoomTool key={name} color={color}/>;
default:
return null;
}
I want to not just display them but do it using the react slick slider. ArrowsComponent and ZoomTool are the components for icons. How to properly wrap this code in <Slider> .. </Slider>?
Here are my two ways
Method 1:
switch (name) {
case Header.Arrows.name:
return (
<Slider>
<ArrowsComponent key={name} color={color}/>
</Slider>
);
case Header.Zoom.name:
return (
<Slider>
<ZoomTool key={name} color={color}/>
</Slider>
);
default:
return null;
}
Method 2
const renderSlider = (child) => {
return (
<Slider>
{child}
</Slider>
);
};
switch (name) {
case Header.Arrows.name:
renderSlider(<ArrowsComponent key={name} color={color}/>);
case Header.Zoom.name:
renderSlider(<ZoomTool key={name} color={color}/>);
default:
return null;
}
Hope that helps.

How can I test a switch case that depends on a parameter?

I have this:
const renderComponents = () => {
switch (selectedService) {
case 'otherservices':
return <SoftLayerCancellationRequests />;
case 'dedicatedhosts':
return <GetDedicatedHostsCancellations />;
case 'virtualguestsservers':
return <SoftLayerGetVirtualGuests />;
case 'baremetalservers':
return <GetBareMetalServersCancellations />;
default:
return null;
}
};
Which at the end is called on the return statement of the component:
return (
<>
<Header pageTitle={t('cancellations.header')} />
{accountId ? (
<>
<TableToolbarComp />
{renderComponents()}
</>
) : (
<UpgradeMessage />
)}
</>
);
And the selectedService parameter is coming from a store:
export default compose(
connect(store => ({
accountId: store.global.softlayerAccountId,
selectedService: store.cancellations.selectedService,
})),
translate(),
hot(module),
)(Cancellations);
What can I do to test that switch case?
The function under renderComponents should accept selectedService as a parameter:
const renderComponents = (selectedService) => {
switch (selectedService) {
// ...
}
};
By not relying on a closure, the function becomes pure and is way easier to unit test :
it('renders a SoftLayerCancellationRequests when passed "otherservices" as parameter', () => {
const wrapper = shallow(renderComponents('otherservices'));
expect(wrapper.find(SoftLayerCancellationRequests)).toHaveLength(1);
})
Yet, I see little value in such tests. This is because the function basically acts as a simple map :
const serviceToComponent : {
otherservices: SoftLayerCancellationRequests,
dedicatedhosts: GetDedicatedHostsCancellations,
virtualguestsservers: SoftLayerGetVirtualGuests,
baremetalservers: GetBareMetalServersCancellations
}
which seems a bit dull to test.
A more meaningful test would be to test the behaviors of the component that uses such a mapping.

Categories