Created
October 17, 2023 16:58
-
-
Save kirill578/fb4ff09e412519935f47e201e027fc9d to your computer and use it in GitHub Desktop.
xstate warn issue
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import { useActor, useMachine } from "@xstate/react"; | |
import React from "react"; | |
import { createMachine, ActorRefFrom, sendParent } from "xstate"; | |
export const id = "survey"; | |
export const eventStoreId = "survey"; | |
export type SurveyActorRef = ActorRefFrom<ReturnType<typeof createSurvey>>; | |
const staticConfig = { | |
id: "survey", | |
predictableActionArguments: true, | |
initial: "questionOne", | |
context: { i: 0 }, | |
schema: { | |
context: {} as { i: number }, | |
events: {} as | |
| { type: "NEXT" } | |
| { type: "CLICK_NO" } | |
| { type: "CLICK_YES" } | |
| { type: "SUB_FLOW_DONE" }, | |
}, | |
states: { | |
done: {}, | |
questionOne: { | |
on: { | |
CLICK_YES: { | |
actions: ["sendAnswerYes"], | |
target: "generalQuestions", | |
}, | |
CLICK_NO: { | |
actions: ["sendAnswerNo"], | |
target: "generalQuestions", | |
}, | |
}, | |
}, | |
generalQuestions: { | |
invoke: { | |
id: "generalQuestions", | |
src: "generalQuestions", | |
}, | |
on: { | |
SUB_FLOW_DONE: { | |
target: "done", | |
}, | |
}, | |
}, | |
}, | |
}; | |
export const createSurvey = (args: { proccessData: (data: string) => void }) => | |
createMachine(staticConfig, { | |
actions: { | |
sendAnswerYes: () => args.proccessData("answerYes"), | |
sendAnswerNo: () => args.proccessData("answerNo"), | |
}, | |
services: { | |
generalQuestions: () => createGeneralQuestionMachine(args), | |
}, | |
}); | |
export type GeneralQuestionsActorRef = ActorRefFrom< | |
ReturnType<typeof createGeneralQuestionMachine> | |
>; | |
const staticGeneralConfig = { | |
id: "generalQuestions", | |
predictableActionArguments: true, | |
initial: "questionAorB", | |
schema: { | |
events: {} as | |
| { type: "NEXT" } | |
| { type: "CLICK_A" } | |
| { type: "CLICK_B" } | |
| { type: "SUB_FLOW_DONE" }, | |
}, | |
states: { | |
done: { | |
entry: ["subflowDone"], | |
}, | |
questionAorB: { | |
on: { | |
CLICK_A: { | |
actions: ["sendAnswerA"], | |
target: "done", | |
}, | |
CLICK_B: { | |
target: "done", | |
actions: ["sendAnswerB"], | |
}, | |
}, | |
}, | |
}, | |
}; | |
export const createGeneralQuestionMachine = (args: { | |
proccessData: (data: string) => void; | |
}) => | |
createMachine(staticGeneralConfig, { | |
actions: { | |
sendAnswerA: () => args.proccessData("answerA"), | |
sendAnswerB: () => args.proccessData("answerB"), | |
subflowDone: sendParent("SUB_FLOW_DONE"), | |
}, | |
}); | |
export function TopLevelGeneralController({ | |
actorRef, | |
}: { | |
actorRef: GeneralQuestionsActorRef; | |
}) { | |
const [state, send] = useActor(actorRef); | |
if (state.matches("questionAorB")) { | |
return ( | |
<div> | |
<>A or B?</> | |
<div role="button" tabIndex={0} onClick={() => send("CLICK_A")}> | |
A | |
</div> | |
<div role="button" tabIndex={0} onClick={() => send("CLICK_B")}> | |
B | |
</div> | |
</div> | |
); | |
} | |
if (state.matches("done")) { | |
return <>done</>; | |
} | |
throw new Error("unreachable"); | |
} | |
export function TopLevelSurveyController({ | |
actorRef, | |
}: { | |
actorRef: SurveyActorRef; | |
}) { | |
const [state, send] = useActor(actorRef); | |
if (state.matches("questionOne")) { | |
return ( | |
<div> | |
<>yes or now?</> | |
<div role="button" tabIndex={0} onClick={() => send("CLICK_YES")}> | |
yes | |
</div> | |
<div role="button" tabIndex={0} onClick={() => send("CLICK_NO")}> | |
no | |
</div> | |
</div> | |
); | |
} | |
if (state.matches("generalQuestions")) { | |
return ( | |
<TopLevelGeneralController | |
actorRef={state.children.generalQuestions as GeneralQuestionsActorRef} | |
/> | |
); | |
} | |
if (state.matches("done")) { | |
return <>done</>; | |
} | |
throw new Error("unreachable"); | |
} | |
const topMachine = createMachine({ | |
predictableActionArguments: true, | |
invoke: { | |
id: "inner", | |
src: "inner", | |
}, | |
}); | |
const useProccessData = () => { | |
const [i, setI] = React.useState(0); | |
React.useEffect(() => { | |
// simulates a hook that changes its reference all the time (could be a third party librar that we don't have control over) | |
const tid = setTimeout(() => setI((a) => a + 1), 1000); | |
return () => clearTimeout(tid); | |
}, [i]); | |
return React.useCallback( | |
(data: string): number => { | |
console.log("posting data", data); | |
return i; | |
}, | |
[i], | |
); | |
}; | |
const App = () => { | |
const proccessData = useProccessData(); | |
const args = React.useMemo( | |
() => ({ | |
proccessData, | |
}), | |
[proccessData], | |
); | |
// this is a simplified usecase, in reality it is used to capture events from children that are triggered with sendParent | |
const [state] = useMachine( | |
React.useMemo( | |
() => | |
createMachine({ | |
predictableActionArguments: true, | |
invoke: { | |
id: "inner", | |
src: "inner", | |
}, | |
}), | |
[], | |
), | |
React.useMemo( | |
() => ({ | |
services: { | |
inner: createSurvey(args).withContext({ | |
i: proccessData("abc"), | |
}), | |
}, | |
}), | |
[proccessData], | |
), | |
); | |
if (!state.children.inner) { | |
return <>loading</>; | |
} | |
return ( | |
<> | |
<TopLevelSurveyController | |
actorRef={state.children.inner as SurveyActorRef} | |
/> | |
</> | |
); | |
}; | |
export default App; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment