How to use different Context on same Component (reactjs) - reactjs

I want to use different context in a common component.
There are 2 providers and 1 common component.
The common component uses different data, and 2 providers have each data indivisually.
e.g.
const useMyProvider1 = () {
return useContext(MyContext1)
}
const MyProvider1 = ({children}) => {
const [data1, setData1] = useState(initial1)
return <MyContext1.Provider value={[data1, setData1]}>{children}</MyContext1.Provider>
}
const useMyProvider2 = () {
return useContext(MyContext2)
}
const MyProvider2 = ({children}) => {
const [data2, setData2] = useState(initial2)
return <MyContext2.Provider value={[data2, setData2]}>{children}</MyContext2.Provider>
}
const Component1 = () => {
return (
<MyProvider1>
<CommonComponent /> // CommonComponent uses data1
</MyProvider1>
)
}
const Component2 = () => {
return (
<MyProvider2>
<CommonComponent /> // Same component but this uses data2
</MyProvider2>
)
}
const CommonComponent = () => {
const {data, setData} = /* I want to use only one provider useMyProvider1 or useMyProvider2 depends on wrapped provider */
return (
<div>this is one of {data}</div>
)
}
It is an idea but not good I think, because CommonComponent depends on each providers.
I want to depend on only wrapped provider.
const CommonComponent = ({providerType}) => {
const {data, setData} = providerType === 'type1' ? useMyProvider1() : useMyProvider2()
return (
<div>this is one of {data}</div>
)
}
Is there any good solutions?

Related

Rendering 3 different components based on click of buttons

I am new to react and need some help on understanding using hooks. I have 3 views I want to render based on 3 different components I want to render based on different TEXT buttons. The buttons would in the same location throughout the components but different text on them!
Component the view will switch from:
const Original = () => {
const [button1, setButton1] = React.useState("button1");
const [button2, setButton2] = React.useState("button2");
const testOriginal = () => {};
const originalView = () => {
setButton1("buttonview1");
setButton2("buttonview2");
return "<div>View 0<button></button><button></button></div>";
};
const view2 = () => {
setButton1("button11view2");
setButton2("button12view2");
return "<div>View 2<button></button><button></button></div>";
};
const view3 = () => {
setButton1("button21view3");
setButton2("button22view3");
return "<div>View 2<button></button><button></button></div>";
};
const ActiveView = () => {
switch (active) {
case 1:
return originalView;
case 3:
return view3;
default:
return view2;
}
};
return ({
ActiveView()
});
};
export default Original;
const View1 = () => {
return <div>View 1</div>;
};
const View2 = () => {
return <div>View 2</div>;
};
export default View2;
const View3 = () => {
return <div>View 3</div>;
};
export default View3;

React Component is rerendering, even though it does not depend on the state

In my React code I have to use a legacy component, which makes a setup api call when it is first rendered. The component has a custom completion/cancelation event which I use to trigger a State update. The current Code looks like this:
export const useOneTimePassword = (
headline = "OTP anfordern",
id = "opt",
type = "sms",
businessProcess = "otp-process"
): UseOneTimePasswordReturn => {
const [otpCode, setOtpCode] = useState<undefined | string>();
const [isOtpCancelled, setIsOtpCancelled] = useState<boolean>(false);
const openOtp = () => {
const otp = document.querySelector(`otp-component#${id}`) as OtpElement;
otp.open();
};
const OtpComponent: FC = () => (
<Otp
headline={headline}
id={id}
type={type}
businessProcess={businessProcess}
setIsOtpCancelled={setIsOtpCancelled}
setOtpCode={setOtpCode}
/>
);
return {
otpCode,
isOtpCancelled,
openOtp,
OtpComponent,
removeOtp: () => {
setOtpCode(undefined);
},
};
};
and for the Component it looks like this:
const Otp: React.FC<OtpProps> = ({
headline,
businessProcess,
type,
id,
setOtpCode,
setIsOtpCancelled,
}) => {
function onOtpResponse(e: CompletedEvent) {
if (e.detail.otpCode) {
setOtpCode(e.detail.otpCode);
setIsOtpCancelled(false);
} else {
setIsOtpCancelled(true);
}
}
const ref = useRef();
useEffect(() => {
//#ts-ignore
if (ref.current) ref.current.addEventListener("completed", onOtpResponse);
}, []);
return (
<otp-component
ref={ref}
headline={headline}
id={id}
type={type}
business-process={businessProcess}
/>
);
};
export default Otp;
What I do not understand is that state changes in otpCode aswell as isOtpCancelled cause a rerender of the OtpComponent

How can I better conditionally render my components?

I have a lot of components being rendered based on different states that I'm using for a game. I'm currently using the method where I check the value of state with ampersands. I'm not sure if there's a different way I should do it, or if there's a more efficient cleaner way to do it.
I've looked up a few different ways but was wondering if someone could maybe give me suggestions for something that would work well with my code I have existing.
const App = () => {
const [startPlayer, setStartPlayer] = useState("");
const [endPlayer, setEndPlayer] = useState("");
const [gameSelected, setGameSelected] = useState(false);
const [gameStarted, setGameStarted] = useState(false);
const [gameWon, setGameWon] = useState(false);
const [winningTeam, setWinningTeam] = useState([]);
const [gameSolved, setGameSolved] = useState(false);
const isMobile = useMobileCheck();
const resetGame = () => {
setStartPlayer("");
setEndPlayer("");
setGameSelected(false);
setGameStarted(false);
setGameWon(false);
setGameSolved(false);
setWinningTeam([]);
};
const setGameType = (gameType) => {
setGameSelected(gameType);
};
const rollPlayers = (startYear, endYear) => {
axios.get(`/api/gettwoplayers?startYear=${startYear}&endYear=${endYear}`).then((res) => {
setStartPlayer(res.data[0]);
setEndPlayer(res.data[1]);
});
};
const startTheGame = () => {
setGameStarted(true);
};
const goBackToGameSelection = () => {
setGameSelected(false);
setGameStarted(false);
setStartPlayer("");
setEndPlayer("");
};
const userSetPlayer = (player, type) => {
if(type === "start") setStartPlayer(player);
if(type === "end") setEndPlayer(player);
};
const theGameWasWon = (history) => {
history.push(endPlayer);
setWinningTeam(history);
setGameWon(true);
};
const solveGame = () => {
setGameSolved(true);
axios.get(`/api/solve?startPlayer=${startPlayer}&endPlayer=${endPlayer}`).then((res) => {
console.log(res.data);
})
};
return (
<Container
sx={{
minHeight:'100vh',
maxWidth: "90vw!important",
}}
>
{
!gameSelected &&
!gameStarted &&
!gameWon &&
<ChooseGame setGameType={setGameType} />
}
{
!gameStarted &&
!gameWon &&
gameSelected === 'r' &&
<CreateRandomGame
rollPlayers={rollPlayers}
startPlayer={startPlayer}
endPlayer={endPlayer}
startTheGame={startTheGame}
goBack={goBackToGameSelection}
/>
}
{
!gameStarted &&
!gameWon &&
gameSelected === 's' &&
<CreateUserGame
startPlayer={startPlayer}
endPlayer={endPlayer}
userSetPlayer={userSetPlayer}
startTheGame={startTheGame}
goBack={goBackToGameSelection}
/>
}
{
!gameWon &&
gameStarted &&
<GameScreen
startPlayer={startPlayer}
endPlayer={endPlayer}
gameWon={theGameWasWon}
resetGame={resetGame}
solveGame={solveGame}
/>
}
{
gameWon &&
<GameWon
resetGame={resetGame}
winningTeam={winningTeam}
/>
}
</Container>
);
}
export default App;
Two things you could try:
Firstly, you've got a lot of boolean state - e.g. gameStarted, and a lot of it seems mutually-exclusive with other boolean state, for example gameStarted looks like it could never be true at the same time as gameWon. In situations like that, it can be a lot nicer to model the state as an enumerated type; unfortunately Javascript doesn't have them natively (look into TypeScript for a "true" enum type) but we can make do with strings:
const MODE_STARTED = 'started'
const MODE_SELECTED_RANDOM = 'random'
const MODE_SELECTED_USER = 'user'
const MODE_GAME_WON = 'won'
...
const [gameMode, setGameMode] = useState(MODE_STARTED);
...
Now rather than flipping individual booleans all over the place, you can just change your game mode ... e.g. setGameMode(MODE_SELECTED_RANDOM)
Once you've done that, your JSX can become cleaner too:
const showCorrectUI = () => {
switch (gameMode) {
case MODE_STARTED:
return <GameScreen {foo} />
case MODE_GAME_WON:
return <GameWon {bar} />
... // etc
}
}
return (
<Container
sx={{
minHeight:'100vh',
maxWidth: "90vw!important",
}}
>
{showCorrectUI()}
</Container>)

Rendered more hooks than during the previous render in NextJS using GraphQL

I'm putting navbar in my _app.js so I don't need to insert it in every component. My problem is that after I login it outputs an error Rendered more hooks than during the previous render. and its pointing it on useQuery(GETCARTDATA
Pls check my code here
const App = ({ Component, pageProps }) => {
const token = getToken()
const [isPopUpShow, setPopUpShow] = useState(false)
const [cartStateData, setCartStateData] = useState([])
const [isCartOpen, setCartOpen] = useState(false)
let cartDetailsData
if (token) {
// eslint-disable-next-line react-hooks/rules-of-hooks
cartDetailsData = useLazyQuery(GETCARTDATA, {
variables: {
page: 1
},
})
// eslint-disable-next-line react-hooks/rules-of-hooks
useMemo(() => {
const cartData = get(cartDetailsData.data, 'findCartDetails.orders') || []
const cartItems = []
if (cartData.length) {
cartData.map(
itm =>
itm.lineItems.length &&
itm.lineItems.map(item => cartItems.push(item))
)
}
setCartStateData(cartItems)
}, [cartDetailsData.data])
}
return (
<>
<div className="app-outer">
{token ? (
<ShowroomHeader
isPopUpShow={isPopUpShow}
setPopUpShow={setPopUpShow}
cartStateData={cartStateData}
cartDetailsData={cartDetailsData}
token={token}
/>
) : (
<Navbar />
)}
</div>
<div className="main">
<Component {...pageProps} />
</div>
</>
)
}
export default withApollo(App)
As #xadmn mentioned, you're rendering your hooks conditionally while React expects the same number of hook calls on every render, thus breaking the rules of Hooks.
You'll need to remove your if statement and move your condition inside a useEffect hook, using useLazyQuery's returned function to execute the query from there. You can also move your useMemo code to the onCompleted callback, since it depends on the results from the query.
const App = ({ Component, pageProps }) => {
const token = getToken()
const [isPopUpShow, setPopUpShow] = useState(false)
const [cartStateData, setCartStateData] = useState([])
const [isCartOpen, setCartOpen] = useState(false)
const [getCardData, cartDetailsData] = useLazyQuery(GETCARTDATA, {
onCompleted: (data) => {
const cartData = get(data, 'findCartDetails.orders') || []
const cartItems = []
if (cartData.length) {
cartData.map(
itm =>
itm.lineItems.length &&
itm.lineItems.map(item => cartItems.push(item))
)
}
setCartStateData(cartItems)
}
})
useEffect(() => {
if (token) {
getCardData({ variables: { page: 1 } })
}
}, [token])
return (
// Your JSX here
)
}

React.Children.count or children.length?

I'm doing on migration from class based components to functional components.
My problem is legacy codes counts react comopnents using children.length which is different my previous use React.Children.count(children).
Any differences between two of these? which one is better?
...
const SomeParentFC = () => {
const arrays = [1,2,3,4,5]
function renderSomething() {
return arrays.map((value,index) => {
<SomeReactComponent key={index}>value</SomeReactComponent>
})
}
return(
<SomeChildFC>
{ renderSomething() }
</SomeChildFC>
)
}
const SomeChildFC = ({children}) => {
const getMyChildrenCount = () => {
? ???
}
return (
<>
</>
)
you can use React.Children.count , documentation here: reactjs.org/docs/react-api.html#reactchildrencount
const getMyChildrenCount = () => React.Children.count(children);

Resources