@strategies/nudge
v1.4.0
Published
Nudge survey suite
Downloads
5
Keywords
Readme
Nudge
Installation
yarn add @strategies/nudge
Usage
Provider
Nudge is a contextual data layer that collects Question
classes and derives the data from their answers.
Because of this contextuality, we must wrap the survey in a provider component.
import { Nudge, NudgeConfig } from '@strategies/nudge';
export default function App() {
return (
<Nudge config={config as NudgeConfig}>
<MySurvey />
</Nudge>
);
}
Survey Context
Once you have provided a context, you can access that context through a hook.
The Survey instance is where you can attach metadata, manually save the survey, or control the question flow. Each of the
behaviors attached to the buttons below will automatically save the survey. Once a survey is completed, the survey is saved,
the survey.completed
flag will be set to true
and may not be reset to false
.
import { Button } from '@strategies/ui';
import { useNudgeSurvey, Survey } from '@strategies/nudge';
export default function MySurvey() {
const survey: Survey = useNudgeSurvey();
const { questions } = survey;
useEffect(() => {
survey.meta = {
origin: window.location.hostname,
};
}, [survey]);
return <>
<Button onClick={() => questions.previous()}>
Previous Question
</Button>
<Button onClick={() => questions.next()}>
Next Question
</Button>
<Button onClick={() => survey.complete()}>
Complete Survey
</Button>
</>;
}
Questions
Now that we have outlined the survey scaffolding and lifecycle we can talk about the Questions.
All questions provided to the survey instance will be extensions of the Question
class. Each question is
required to have a unique id
property. Optionally, each question can specify which question is next with the
to
property. If the to
property is not specified for a question, there is a method on the Survey
instance that
let's you dynamically choose which question to navigate to. We will cover this in the next section.
export class ThirdQuestion extends Question {
id = 'third';
}
export class SecondQuestion extends Question {
id = 'second';
}
export class FirstQuestion extends Question {
id = 'first';
to = SecondQuestion;
}
Then you create an instance of each question to be used by your app through the QuestionProvider
component.
function First() {
return (
<QuestionProvider question={FirstQuestion}>
{(question: FirstQuestion) => question.active && (
<div className="Question First">
{/* use `question` here */}
</div>
)}
</QuestionProvider>
)
}
Answers
All Question
s are required to have an answer
getter which are collected by the survey each save.
Any data that you wish to save as an answer to the question should be provided in that getter:
export class SecondQuestion extends Question {
id = 'second';
@observable
value: string = '';
@action
setValue(value: string) {
this.value = value;
}
get answer() {
return {
value: this.value,
}
}
}
export class FirstQuestion extends Question {
id = 'first';
to = SecondQuestion;
@observable
values: number[] = [];
@action
add(value: number) {
this.values.push(value);
}
@action
remove(value: number) {
this.values.splice(this.values.indexOf(value), 1);
}
get answer() {
return {
values: this.values,
}
}
}
SingleValueQuestion & MultiValueQuestion
The above patterns are used so often, Nudge has those question types built-in!
export class SecondQuestion extends MultiValueQuestion<number> {
id = 'second';
}
export class FirstQuestion extends SingleValueQuestion<string> {
id = 'first';
to = SecondQuestion;
}
Question Graph
Sometimes the path forward through a survey is determined by how a question has been answered. This is why we don't declare the questions in a linear array and use a graph with nodes that we can jump to and around.
In order to call upon other questions, we just have to query for a reference to an instance of a Question
class.
function Second() {
const { questions } = useNudgeSurvey();
const first = questions.use(FirstQuestion);
const third = questions.use(ThirdQuestion);
return (
<QuestionProvider question={SecondQuestion}>
{(second: SecondQuestion) => second.active && (
<div className="Question Second">
<p>You answered {first.value} for the first question</p>
<input value={second.value} onChange={e => second.setValue(e.target.value)} />
<Button onClick={() => questions.to(third)}>
To Question #3
</Button>
</div>
)}
</QuestionProvider>
);
}
Complete Example
import {
Nudge,
MultiValueQuestion,
SingleValueQuestion,
Question,
useNudgeSurvey,
} from '@strategies/nudge';
export class ThirdQuestion extends Question {
id = 'third';
constructor() {
super();
makeObservable(this);
}
person: new SingleValueQuestion<string>();
@observable
robots: string[] = [];
@action
addRobot(robot: string) {
this.robots.push(robot);
}
get answer() {
return {
people: this.person.value,
robots: this.robots,
};
}
}
export class SecondQuestion extends MultiValueQuestion<number> {
id = 'second';
}
export class FirstQuestion extends SingleValueQuestion<string> {
id = 'first';
to = SecondQuestion;
}
function First() {
return (
<QuestionProvider question={FirstQuestion}>
{(question: FirstQuestion) => question.active && <Observer>{() => (
<div className="Question First">
{[...'x'.repeat(5)].map((_, i: number) => (
<Button onClick={() => question.values.includes(i+1) ? question.remove(i+1) : question.add(i+1)}>
{i+1}
</Button>
))}
</div>
)}</Observer>}
</QuestionProvider>
)
}
const Second = observer(function Second() {
const { questions } = useNudgeSurvey();
const first = questions.use(FirstQuestion);
const third = questions.use(ThirdQuestion);
return (
<QuestionProvider question={SecondQuestion}>
{(second: SecondQuestion) => second.active && <Observer>{() => (
<div className="Question Second">
<p>You answered {first.value} for the first question</p>
<input value={second.value} onChange={e => second.setValue(e.target.value)} />
<Button onClick={() => questions.to(third)}>
To Question #3
</Button>
</div>
)}</Observer>}
</QuestionProvider>
);
});
function Third() {
const [robot, setRobot] = useState<string>('');
return (
<QuestionProvider question={ThirdQuestion}>
{(question: ThirdQuestion) => question.active && <Observer>{() => (
<div className="Question Third">
<input value={question.person.value} onChange={e => question.person.setValue(e.target.value)} />
<input value={robot} onChange={e => setRobot(e.target.value)} />
<Button onClick={() => { question.addRobot(robot); setRobot('') }}>
Add Robot
</Button>
<ul>
{question.robots.map((robot: string) => <li key={robot}>{robot}</li>)}
</ul>
</div>
)}</Observer>}
</QuestionProvider>
)
}
function Survey() {
const survey = useNudgeSurvey();
const { questions } = survey;
useEffect(() => {
survey.meta = {
origin: window.location.hostname,
};
}, [survey]);
return (
<div className="Survey">
{survey.complete ? (
<p>Thank you!</p>
) : <>
<First />
<Second />
<Third />
</>}
<div className="buttons">
<Button onClick={() => questions.previous()}>
Previous Question
</Button>
<Button onClick={() => questions.next()}>
Next Question
</Button>
<Button onClick={() => survey.complete()}>
Complete Survey
</Button>
</div>
</div>
</>;
}
export default function App() {
return (
<Nudge config={config}>
<Survey />
</Nudge>
);
}