This project I am working with has an onboarding Wizard, basically some code to deal with the step by step onboarding process almost similar to what you see here:
https://medium.com/@l_e/writing-a-wizard-in-react-8dafbce6db07
except this one supposedly has a function to convert a component or step into a route:
convertStepToRoute = step => {
const Component = StepComponents[step.component || ''];
return Component
? <Route
key={step.key}
path={`${WizardLayout.pathname}/${step.url}`}
render={this.renderRouteComponent(Component)}
/>
: null;
};
StepComponents
comes from import StepComponents from '../Steps';
which is a directory with all the components, they were six now seven of them that are supposed to walk the user through the onboarding process.
And it's my understanding that they are pulled from the index.js
file inside of Steps/
directory similar to how there would be a root reducer file in a reducers folder to export all of them, the steps component in this case like so:
import glamorous from "glamorous";
import ThemedCard from "../../ThemedCard";
import BusinessAddress from "./BusinessAddress";
import CreatePassword from "./CreatePassword";
import GetInvolved from "./GetInvolved";
import Representatives from "./Representatives";
import Topics from "./Topics";
import MemberBenefits from "./MemberBenefits";
export const StepHeader = glamorous.div({
marginBottom: 20,
marginTop: 20,
fontSize: "2rem",
color: "#757575"
});
const OnboardingCompleted = glamorous(ThemedCard)({
textAlign: "center",
boxShadow: "none !important"
});
export default {
CreatePassword,
BusinessAddress,
Completed: OnboardingCompleted,
GetInvolved,
MemberBenefits,
Topics,
Representatives
};
Well, I added mine MemberBenefits
and it does not seem to work, it's not rendering with its corresponding route. Where could it not be registering this new step or component?
Okay so the magic is not happening inside of Onboarding/OnBoardingWizard/index.js
, it's happening inside of Wizard/WizardEngine.js
:
import React from "react";
import PropTypes from "prop-types";
import objectToArray from "../../../../common/utils/object-to-array";
// TODO: figure out how to use this without making children of wizard engine tied to wizardStep
// eslint-disable-next-line no-unused-vars
class WizardStep {
constructor({ component, color, order, render }, stepComponents) {
if (!component || !render) {
throw new Error("Component or render must be provided.");
}
let componentValue;
if (component) {
componentValue = this.resolveComponent(component, stepComponents);
if (!!componentValue && !React.isValidElement(componentValue)) {
throw new Error(
"wizard step expected component to be a valid react element"
);
}
} else if (render && typeof render === "function") {
throw new Error("wizard step expected render to be a function");
}
this.Component = componentValue;
this.color = color;
this.order = order;
this.render = render;
}
resolveComponent = (component, stepComponents) => {
const componentValue = component;
if (typeof component === "string") {
const componentValue = stepComponents[component];
if (!componentValue) {
throw new Error("component doesnt exist");
}
}
return componentValue;
};
}
export default class WizardEngine extends React.Component {
static propTypes = {
steps: PropTypes.oneOfType([PropTypes.object, PropTypes.array]),
initActiveIndex: PropTypes.oneOfType([PropTypes.func, PropTypes.number]),
stepComponents: PropTypes.object
};
constructor(props) {
super(props);
this.state = {
activeIndex: this.resolveInitActiveIndex(props),
steps: this.buildStepsFromConfig(props)
};
}
componentWillReceiveProps(nextProps) {
this.setState({ steps: this.buildStepsFromConfig(nextProps) });
}
resolveInitActiveIndex = props => {
const { initActiveIndex } = props;
let activeIndex = 0;
if (typeof initActiveIndex === "function") {
activeIndex = initActiveIndex(props);
}
if (typeof initActiveIndex === "number") {
activeIndex = initActiveIndex;
}
return activeIndex;
};
buildStepsFromConfig = props => {
const { steps } = props;
let stepArr = steps;
// validate stepList
if (typeof steps === "object" && !Array.isArray(steps)) {
stepArr = objectToArray(steps);
}
if (!Array.isArray(stepArr)) {
throw new Error(
`Unsupported Parameter: Wizard Engine(steps) expected either (object, array); got ${typeof stepArr}`
);
}
return stepArr;
// return stepArr.map(step => new WizardStep(step));
};
setActiveIndex = activeIndex => {
this.setState({ activeIndex });
};
goForward = () => {
this.setState(prevState => ({
activeIndex: prevState.activeIndex + 1
}));
};
goBack = () => {
this.setState(prevState => ({
activeIndex: prevState.activeIndex - 1
}));
};
render() {
const { children } = this.props;
const childProps = {
...this.state,
setActiveIndex: this.setActiveIndex,
goForward: this.goForward,
goBack: this.goBack,
currentStep: this.state.steps[this.state.activeIndex]
};
if (Array.isArray(children)) {
return (
<div>
{children.map((child, i) => {
if (typeof child === "function") {
return child(childProps);
}
childProps.key = `${child.type.name}_${i}`;
return React.cloneElement(child, childProps);
})}
</div>
);
}
if (typeof children === "function") {
return children(childProps);
}
return children;
}
}
The path URL is being mapped on the backend utilizing the Entity Framework similar to the setup you can view here in this documentation:
https://dzone.com/articles/aspnet-core-crud-with-reactjs-and-entity-framework
except it is being done in Express.
So it's not using React-Router in the traditional sense where Express allows it to control the whole mapping route paths to components, but instead the path to the onboarding component is being mapped here inside the Express src/app-server/apiConfig.js
like so:
"get-involved-onboarding": {
title: "Get Involved",
url: "/account/onboarding/get-involved",
icon: "explore",
component: "GetInvolved",
progress: {
stepType: "GetInvolved",
hasCompleted: true
}
},