Reset Box in Tic Tac Toe with React - css

I am having issue with resetting the game whenever I click on reset button.
I have created tic tac toe game with react hooks with 2 components Game and
Box.How can I add reset button with the use of React hooks.
I am Trying to make a with the code below.Also I tried to add reset button in it but is not working as required.Please help me out on how I can use reset button correctly to reset the box to empty.
Here is my Box component.
import React, { useState } from 'react'
import './style.css'
function Box(props) {
const [text, setText] = useState('')
function toggleText() {
if(text === '') {
setText(props.currentState)
props.changeTurn(props.row, props.col)
}
}
return <div className="box" onClick={toggleText}>{text}</div>
}
export default Box
Here is my Game Component.
import './style.css'
import Box from '../Box'
const board = [[],[],[]]
function Game() {
const [turn, setTurn] = useState('X')
const [winningtext, setWinningText] = useState('')
console.log(board)
function changeTurn(row, col) {
board[row][col] = turn
setTurn(turn => turn === 'X' ? 'O' : 'X' )
//console.log(board.length)
const winner = checkForWin()
//console.log(winner)
if(winner) {
setWinningText(winner + ' Won!')
}
}
// Winning game logic
function checkForWin() {
// row test
for(let i = 0; i < board.length; i++) {
const row = board[i]
//console.log(row[0])
if(row[0] === row[1] && row[1] === row[2] && row[0]){
return row[0]
}
}
//column test
for(let i = 0; i < board.length; i++) {
const el1 = board[0][i]
const el2 = board[1][i]
const el3 = board[2][i]
//console.log(`${el1} ${el2} ${el3}`)
if(el1 === el2 && el2 === el3 && el1) {
return el1
}
}
//diagonal test
const d1 = board[0][0]
const d2 = board[1][1]
const d3 = board[2][2]
if(d1 === d2 && d2 === d3 && d1) {
return d1
}
const p1 = board[0][2]
const p2 = board[1][1]
const p3 = board[2][0]
if(p1 === p2 && p2 === p3 && p1) {
return p1
}
return false
}
function reset() {
//Clear all grids and winner message
setWinningText('')
}
return <div className="game">
<h1>Tic Tac Toe</h1><br/>
<button className="reset" onClick={reset}>Reset</button><br/><br/>
<div id="winning-text">{winningtext}</div>
<div className="row row-1">
<Box row={0} col={0} currentState={turn} changeTurn={changeTurn} />
<Box row={0} col={1} currentState={turn} changeTurn={changeTurn} />
<Box row={0} col={2} currentState={turn} changeTurn={changeTurn} />
</div>
<div className="row row-2">
<Box row={1} col={0} currentState={turn} changeTurn={changeTurn} />
<Box row={1} col={1} currentState={turn} changeTurn={changeTurn} />
<Box row={1} col={2} currentState={turn} changeTurn={changeTurn} />
</div>
<div className="row row-3">
<Box row={2} col={0} currentState={turn} changeTurn={changeTurn} />
<Box row={2} col={1} currentState={turn} changeTurn={changeTurn} />
<Box row={2} col={2} currentState={turn} changeTurn={changeTurn} />
</div>
</div>
}
export default Game

In general, if there is "state" (eg: boards) you want that to be within a React component. You could reset boards by setting it to the same initial value (eg: [[], [], []]) but it will not re-render because React won't know it changed. The suggestion I offered below is to move boards to use the useState hook so whenever it changes React can properly re-render.
Another issue is that both board and the local value in the Box component were storing similar state so it would be a challenge to keep them in sync. Rather, having a single board value in the parent and passing a callback to the Box will probably make your life easier and avoid weird data synchronization issues.
You didn't share the CSS so I added some basic styles. Consider using CSS grid for this (seems like a perfect use case).
Consider using a map for each row and column to reduce the need for the duplication (this is simple enough it's not a big deal, but with this, you could easily make it a 5 x 5 grid or something).
I made as few changes as possible to get it working, so might have missed some things.
function Box(props) {
const { value, changeTurn, row, col } = props;
function toggleText() {
if (!value) {
changeTurn(row, col);
}
}
return (
<div className="box" onClick={toggleText}>
{value}
</div>
);
}
function Game() {
const [board, setBoard] = React.useState([[], [], []]);
const [turn, setTurn] = React.useState("X");
const [winningtext, setWinningText] = React.useState("");
console.log(board);
function changeTurn(row, col) {
const newBoard = [...board];
const newRow = [...board[row]];
newBoard[row] = newRow;
newBoard[row][col] = turn;
setBoard(newBoard);
setTurn(turn => (turn === "X" ? "O" : "X"));
//console.log(board.length)
const winner = checkForWin();
//console.log(winner)
if (winner) {
setWinningText(winner + " Won!");
}
}
// Winning game logic
function checkForWin() {
// row test
for (let i = 0; i < board.length; i++) {
const row = board[i];
//console.log(row[0])
if (row[0] === row[1] && row[1] === row[2] && row[0]) {
return row[0];
}
}
//column test
for (let i = 0; i < board.length; i++) {
const el1 = board[0][i];
const el2 = board[1][i];
const el3 = board[2][i];
//console.log(`${el1} ${el2} ${el3}`)
if (el1 === el2 && el2 === el3 && el1) {
return el1;
}
}
//diagonal test
const d1 = board[0][0];
const d2 = board[1][1];
const d3 = board[2][2];
if (d1 === d2 && d2 === d3 && d1) {
return d1;
}
const p1 = board[0][2];
const p2 = board[1][1];
const p3 = board[2][0];
if (p1 === p2 && p2 === p3 && p1) {
return p1;
}
return false;
}
function reset() {
setBoard([[], [], []]);
setWinningText("");
}
return (
<div className="game">
<h1>Tic Tac Toe</h1>
<br />
<button className="reset" onClick={reset}>
Reset
</button>
<br />
<br />
<div id="winning-text">{winningtext}</div>
<div className="boxes">
{[0, 1, 2].map(row => {
return [0, 1, 2].map(col => {
return (
<Box
row={row}
col={col}
key={`${row}-${col}`}
value={board[row][col]}
currentState={turn}
changeTurn={changeTurn}
/>
);
});
})}
</div>
</div>
);
}
const rootElement = document.getElementById("root");
ReactDOM.render(<Game />, rootElement);
.boxes {
display: grid;
grid-template-columns: 32px 32px 32px;
}
.box {
border: 1px solid red;
width: 32px;
height: 32px;
}
<script crossorigin src="https://unpkg.com/react#16/umd/react.development.js"></script>
<script crossorigin src="https://unpkg.com/react-dom#16/umd/react-dom.development.js"></script>
<div id="root"></div>

You could make the box component stateless, so you move the state logic from each box component out of it into the game component instead. You already have an array for the whole board, so why not just pass the state from the board to each box instead? Then you can also reset the board array, which would reset all boxes.
Check the following example. This hasn't been tested, but think it would work.
import React from 'react'
import './style.css'
function Box(props) {
function onClick() {
props.changeTurn(props.row, props.col)
}
return <div className="box" onClick={onClick}>{props.currentState}</div>
}
export default Box
import './style.css'
import Box from '../Box'
const board = [[],[],[]];
function Game() {
const [turn, setTurn] = useState('X')
const [winningtext, setWinningText] = useState('')
console.log(board)
function changeTurn(row, col) {
board[row][col] = turn
setTurn(turn => turn === 'X' ? 'O' : 'X' )
//console.log(board.length)
const winner = checkForWin()
//console.log(winner)
if(winner) {
setWinningText(winner + ' Won!')
}
}
// Winning game logic
function checkForWin() {
// row test
for(let i = 0; i < board.length; i++) {
const row = board[i]
//console.log(row[0])
if(row[0] === row[1] && row[1] === row[2] && row[0]){
return row[0]
}
}
//column test
for(let i = 0; i < board.length; i++) {
const el1 = board[0][i]
const el2 = board[1][i]
const el3 = board[2][i]
//console.log(`${el1} ${el2} ${el3}`)
if(el1 === el2 && el2 === el3 && el1) {
return el1
}
}
//diagonal test
const d1 = board[0][0]
const d2 = board[1][1]
const d3 = board[2][2]
if(d1 === d2 && d2 === d3 && d1) {
return d1
}
const p1 = board[0][2]
const p2 = board[1][1]
const p3 = board[2][0]
if(p1 === p2 && p2 === p3 && p1) {
return p1
}
return false
}
function reset() {
//Clear all grids and winner message
const board = [[],[],[]];
}
return <div className="game">
<h1>Tic Tac Toe</h1><br/>
<button className="reset" onClick={reset}>Reset</button><br/><br/>
<div id="winning-text">{winningtext}</div>
<div className="row row-1">
<Box row={0} col={0} currentState={board[0][0]} changeTurn={changeTurn} />
<Box row={0} col={1} currentState={board[0][1]} changeTurn={changeTurn} />
<Box row={0} col={2} currentState={board[0][2]} changeTurn={changeTurn} />
</div>
<div className="row row-2">
<Box row={1} col={0} currentState={board[1][0]} changeTurn={changeTurn} />
<Box row={1} col={1} currentState={board[1][1]} changeTurn={changeTurn} />
<Box row={1} col={2} currentState={board[1][2]} changeTurn={changeTurn} />
</div>
<div className="row row-3">
<Box row={2} col={0} currentState={board[2][0]} changeTurn={changeTurn} />
<Box row={2} col={1} currentState={board[2][1]} changeTurn={changeTurn} />
<Box row={2} col={2} currentState={board[2][2]} changeTurn={changeTurn} />
</div>
</div>
}
export default Game

Related

react-pdf: how to view pdf and return to sender

I have successfully implemented react-pdf. The pdf has 3 navigation
buttons - Return, Previous, and Next. The button that does not work
is "Return". The functioning components are Send.js and RenderPDF.js.
Send.js displays a list of documents with an "Invoice" button adjacent
to each item. Clicking Invoice sends a prop (pdf name) to
RenderPDF.js, which renders the pdf. I have no graceful means of
returning to Send.js after viewing the pdf. Using history.push(), I
can return to any other page in my site except Send.js. Any
suggestions?
send.js
import React, {
useContext,
useEffect,
useState
} from 'react';
import PageTitle from '../components/common/PageTitle';
import SendPageHeading from '../components/common/SendPageHeading';
import {
FetchContext
} from '../context/FetchContext';
import ButtonDanger from '../components/common/ButtonDanger';
import ButtonLookup from '../components/common/ButtonLookUp';
import FormError from '../components/FormError';
import {
RenderPDF
} from '../components/common/RenderPDF.js';
const SendItemContainer = ({
children
}) => ( <
div className = "bg-white rounded shadow-md mb-2 p-0" > {
children
} < /div>
);
const SendItem = ({
item,
handlePdfProcess,
onDelete
}) => {
return ( <
div className = "flex" >
<
div className = "flex flex-row w-full" >
<
div className = "flex flex-row w-full my-1 " >
<
div className = "relative text-sm w-8 left-0 text-gray-600" > {
item.companyKey
} <
/div> <
div className = "relative text-sm w-88 left-50 text-gray-600" > {
item.companyName
} <
/div> <
div className = "relative text-sm w-8 left-100 text-gray-600" > {
item.docType
} <
/div> <
div className = "relative text-sm w-8 left-125 text-gray-600" > {
item.docNo
} <
/div> <
div className = "relative text-sm w-24 left-175 text-gray-600" > {
item.docDate.substr(5, 2) +
'-' +
item.docDate.substr(8, 2) +
'-' +
item.docDate.substr(0, 4)
} <
/div> <
div className = "relative text-sm w-94 left-225"
style = {
item.emailTo.indexOf(' ') === -1 ?
{
color: '#212529'
} :
{
color: '#e53e3e',
fontWeight: 'bold'
}
} >
{
item.emailTo
} <
/div> <
div className = "relative text-sm w-8 left-275 text-gray-600" > {
item.emailStatus
} <
/div> <
/div> <
div className = "flex flex-row self-end mr-3" >
<
ButtonLookup text = "Invoice"
onClick = {
() => handlePdfProcess(item)
}
/> <
ButtonDanger text = "Delete"
onClick = {
() => onDelete(item)
}
/> <
/div> <
/div> <
/div>
);
};
const Send = () => {
const fetchContext = useContext(FetchContext);
const [docData, setDocData] = useState([]);
const [pdfData, setPdfData] = useState();
const [pdfFlag, setPdfFlag] = useState(false);
const [errorMessage, setErrorMessage] = useState();
useEffect(() => {
const getSend = async () => {
try {
const {
data
} = await fetchContext.authAxios.get('send');
setDocData(data);
} catch (err) {
console.log('the err', err);
}
};
getSend();
}, [fetchContext]);
const onDelete = async item => {
try {
if (window.confirm('Are you sure you want to delete this item?')) {
const {
data
} = await fetchContext.authAxios.delete(
`docData/${item.id}`
);
setDocData(docData.filter(item => item.id !== data.modifiedItem.id));
}
} catch (err) {
const {
data
} = err.response;
setErrorMessage(data.message);
}
};
const handlePdfProcess = item => {
// const docName = item.docName;
setPdfData(`${item.docName}`);
setPdfFlag(true);
console.log(`🌵 pdfFlag: ${pdfFlag}`);
};
return pdfFlag ? ( <
>
<
RenderPDF dataFromSend = {
pdfData
}
/> <
/>
) : ( <
>
<
PageTitle title = "Invoice Staging" / >
<
SendPageHeading / > {
errorMessage && < FormError text = {
errorMessage
}
/>} {
docData && docData.length ?
docData.map(item => ( <
SendItemContainer key = {
item.id
} >
<
SendItem item = {
item
}
handlePdfProcess = {
handlePdfProcess
}
/> <
SendItem item = {
item
}
onDelete = {
onDelete
}
/> <
/SendItemContainer>
)) :
'No Document Items'
} <
/>
);
};
export default Send;
< RenderPDF.js
import React, {
useState
} from 'react';
import {
Document,
Page,
pdfjs
} from 'react-pdf/dist/esm/entry.webpack';
import {
useHistory
} from 'react-router-dom';
pdfjs.GlobalWorkerOptions.workerSrc = '/pdf.worker.js';
export const RenderPDF = dataFromSend => {
const [numPages, setNumPages] = useState(null);
const [pageNumber, setPageNumber] = useState(1); //setting 1 to show fisrt page
const history = useHistory(null);
function onDocumentLoadSuccess({
numPages
}) {
setNumPages(numPages);
setPageNumber(1);
}
const findValueByPrefix = (object, prefix) => {
for (var property in object) {
if (
object.hasOwnProperty(property) &&
property.toString().startsWith(prefix)
) {
return object[property];
}
}
};
let pdfDoc = findValueByPrefix(dataFromSend, 'dataFromSend');
pdfDoc = `/pdfs/${pdfDoc}`;
console.log(`🔥 prop: ${pdfDoc}`);
function changePage(offset) {
setPageNumber(prevPageNumber => prevPageNumber + offset);
}
function previousPage() {
changePage(-1);
}
function nextPage() {
changePage(1);
}
const return2Send = () => {
let path = '/send';
history.push(path);
};
return ( <
>
<
Document file = {
pdfDoc
}
onLoadSuccess = {
onDocumentLoadSuccess
} >
<
Page pageNumber = {
pageNumber
}
/> <
/Document> <
div >
<
p >
Page {
pageNumber || (numPages ? 1 : '--')
}
of {
numPages || '--'
} <
/p> <
button type = "button"
style = {
{
marginRight: '24px'
}
}
onClick = {
return2Send
} >
Return <
/button> <
button type = "button"
disabled = {
pageNumber <= 1
}
style = {
{
marginRight: '24px'
}
}
onClick = {
previousPage
} >
Previous <
/button> <
button type = "button"
disabled = {
pageNumber >= numPages
}
onClick = {
nextPage
} >
Next <
/button> <
/div> <
/>
);
};

Need help adding function to NFT minting dapp built with next.js

I am building an NFT minting dapp, where users can claim 1 NFT only. I need to add this function to it to check if a user has already minted the NFT, and if they have to return an error message.
This is a link to the API docs: https://portal.thirdweb.com/react/react.useclaimineligibilityreasons
I'm using Thirdweb, and while I have everything else working OK I am just too inexperienced to implement this function without help. What would it look like? How do I "check" if they already have one? Honestly just a nudge in the right direction would help.
I am guessing this function would go into my buttons.js component:
import {
useAddress,
useDisconnect,
useMetamask,
useEditionDrop,
useChainId,
getTotalCount,
} from "#thirdweb-dev/react";
import { useEffect, useState } from "react";
import ReactLoading from "react-loading";
import { toast } from "react-toastify";
import "react-toastify/dist/ReactToastify.css";
import Wrongchain from "./wrongchain";
const Buttons = (props) => {
const chainId = useChainId();
console.log(chainId);
// const [totalSupply, setTotalSupply] = useState(0);
const [inProgress, setInProgress] = useState(false);
const [completed, setCompleted] = useState(false);
// const [isConnected, setConnected] = useState(false);
const address = useAddress();
const connectWithMetamask = useMetamask();
const disconnect = useDisconnect();
const editionDrop = useEditionDrop(
"0xB0861Ef3C80096c4434E027a4C650CC47D5614C0"
);
console.log(address);
console.log(props.id);
// const connected = async () => {
// if (connectWithMetamask && address) {
// setConnected(true);
// }
// };
const mint = async () => {
if (editionDrop && address) {
setInProgress(true);
// set a condition where if the txn doens't happen, in progress reverts back to false
const tx = await editionDrop.claimTo(address, props.id, 1);
console.log(tx);
setInProgress(false);
setCompleted(true);
toast.success("Mint Succesful!");
}
};
const startOver = () => {
setCompleted(false);
setInProgress(false);
disconnect();
};
const targetUrl = `https://testnets.opensea.io/assets/mumbai/0xB0861Ef3C80096c4434E027a4C650CC47D5614C0/${props.id}`;
return (
<div>
<ButtonContainer className="gap-8">
{
// provider ? (
// startApp(provider)
// ) :
address ? (
// update chainId to 137 for mainnet
chainId === 80001 ? (
<Mint>
<TitleContainer className="grid gap-6">
<Title>{props.title}</Title>
<h2 className="font-light">{props.des}</h2>
</TitleContainer>
<div className="flex gap-6">
{completed ? (
<a href={targetUrl} target="_blank">
<button className="bg-white rounded-full transition duration-600 hover:scale-105 border-2 border-black text-black mt-[20px] py-3 px-7">
View on OpenSea
</button>
</a>
) : inProgress ? (
<div className="pt-4">
<ReactLoading
type="bubbles"
color="#FFFFFF"
height={30}
width={65}
/>
</div>
) : (
<FilledButton disabled={inProgress} onClick={mint}>
<>Mint</>
</FilledButton>
)}
<UnfilledButton onClick={startOver} disabled={inProgress}>
Disconnect
</UnfilledButton>
</div>
</Mint>
) : (
<Wrongchain />
)
) : (
<Mint>
<TitleContainer className="grid gap-6">
<Title>{props.connect}</Title>
<h2>{props.connectdes}</h2>
</TitleContainer>
<FilledButton onClick={connectWithMetamask}>
Connect Wallet
</FilledButton>
</Mint>
)
}
</ButtonContainer>
</div>
);
};
export default Buttons;

React Wait until transition end for vertical scroll

I'm trying to make vertical scroll page looks like fullpage.js with react
https://alvarotrigo.com/fullPage/
Page scroll works well but having hard time to prevent double scroll..
I want to make scroll doesn't work until finish move to next page.
Using setTimeout and clearTimeout works well but it has pre delay before moving pages.
I don't like that so tried to use onTransitionEnd to detect change but doesn't work well.
Currently my code is like this
const Container = () => {
const [index, setIndex] = useState(0);
const [isAnimating, setAnimating] = useState(false);
const ref = useRef<HTMLDivElement>(null);
const animatingRef = useRef(false);
const indexRef = useRef(0);
indexRef.current = index;
useEffect(() => {
if (!isAnimating) {
const updater = (e: WheelEvent) => {
setAnimating(true);
let dir = e.deltaY;
if (dir < 0) {
indexRef.current > 0 && setIndex(indexRef.current - 1);
} else {
indexRef.current < 2 && setIndex(indexRef.current + 1);
}
};
window.addEventListener('wheel', e => updater(e));
return () => {
window.removeEventListener('wheel', e => updater(e));
};
}
}, [isAnimating]);
useEffect(() => {
if (ref && ref.current) {
ref.current.style.transition = 'all 0.5s ease-in-out';
ref.current.style.transform = `translate3d(0,-${index * 100}%,0)`;
}
}, [index]);
return (
<Wrapper>
<ViewContainer ref={ref} onTransitionEnd={() => setAnimating(false)}>
<View color="orange" />
<View color="red" />
<View color="yellow" />
</ViewContainer>
</Wrapper>
);
};

How to access the div element with a key in React JS? (react-dnd)

This is a small app created using react-beautiful-dnd. I want to add individual css element for different key values passed through this div element. However I am unable to access this specific div element for the specific key value in css.
<div key={key} className={"column"}>
<ProjectWrapper className="border">
<hr className="hr"></hr>
<h3 className="title">{data.title}</h3>
</ProjectWrapper>
The entire source code:
import React, {useState} from 'react';
import './App.css';
import {DragDropContext, Droppable, Draggable} from "react-beautiful-dnd";
import _ from "lodash";
import {v4} from "uuid";
const item = {
id: v4(),
name: "Clean the house"
}
const item2 = {
id: v4(),
name: "Wash the car"
}
function App() {
const [text, setText] = useState("")
const [state, setState] = useState({
//creating 3 columns
"todo": {
title: "Todo",
items: [item, item2]//temporary data valuesa
},
"in-progress": {
title: "In Progress",
items: []
},
"done": {
title: "Completed",
items: []
}
})
const handleDragEnd = ({destination, source}) => {
if (!destination) {
return
}
if (destination.index === source.index && destination.droppableId === source.droppableId) {
return
}
// Creating a copy of item before removing it from state
const itemCopy = {...state[source.droppableId].items[source.index]}
setState(prev => {
prev = {...prev}
// Remove from previous items array
prev[source.droppableId].items.splice(source.index, 1)
// Adding to new items array location
prev[destination.droppableId].items.splice(destination.index, 0, itemCopy)
return prev
})
}
const addItem = () => {
setState(prev => {
return {
...prev,
todo: {
title: "Todo",
items: [
{
id: v4(),
name: text
},
...prev.todo.items
]
}
}
})
setText("")
}
//the dragdropcontext is the wrapper for draging elements
//dropable is the certain area inside the column where u can drop items
//dragble are the items in the column to be dragged
//here {_.map(state, (data, key) => {
//the above function is used to return the data and key of all 3 elements mentioned under use state() above
//the key: returns todo,inprogress,and done
//where as the data returns all the values of the variables within each key
return (
<div className="App">
<div>
<input type="text" value={text} onChange={(e) => setText(e.target.value)}/>
<button onClick={addItem}>Add</button>
</div>
<DragDropContext onDragEnd={handleDragEnd}>
{_.map(state, (data, key) => {
return(
<div key={key} className={"column"}>
<h3>{data.title}</h3>
<Droppable droppableId={key}>
{(provided, snapshot) => {
return(
<div
ref={provided.innerRef}
{...provided.droppableProps}
key = {"Todo"}
className={"droppable-col"}
>
{data.items.map((el, index) => {
return(
<Draggable key={el.id} index={index} draggableId={el.id}>
{(provided, snapshot) => {
console.log(snapshot)
return(
<div
className={`item ${snapshot.isDragging && "dragging"}`}
ref={provided.innerRef}
{...provided.draggableProps}
{...provided.dragHandleProps}
>
{el.name}
</div>
)
}}
</Draggable>
)
})}
{provided.placeholder}
</div>
)
}}
</Droppable>
</div>
)
})}
</DragDropContext>
</div>
);
}
export default App;
May I suggest using a utility like clsx
It will apply the specific class based on your value like so:
import clsx from 'clsx';
export default function App() {
const key = 'todo'; // or 'in-progress' or 'done'
return (
<div className={clsx('existingClass', key)}>
.
.
.
</div>
);
}
Which renders as <div class="existingClass todo">...</div>. You can configure your CSS classnames for the dynamic values. (in this case, todo)
// https://github.com/lukeed/clsx/blob/master/src/index.js
// MIT © Luke Edwards
function toVal(mix) {
var k, y, str='';
if (typeof mix === 'string' || typeof mix === 'number') {
str += mix;
} else if (typeof mix === 'object') {
if (Array.isArray(mix)) {
for (k=0; k < mix.length; k++) {
if (mix[k]) {
if (y = toVal(mix[k])) {
str && (str += ' ');
str += y;
}
}
}
} else {
for (k in mix) {
if (mix[k]) {
str && (str += ' ');
str += k;
}
}
}
}
return str;
}
function clsx() {
var i=0, tmp, x, str='';
while (i < arguments.length) {
if (tmp = arguments[i++]) {
if (x = toVal(tmp)) {
str && (str += ' ');
str += x
}
}
}
return str;
}
// https://github.com/lukeed/clsx/blob/master/src/index.js
// MIT © Luke Edwards
//-------------------
function App() {
const key = ['todo', 'in-progress', 'done'];
return (
<div className="App">
{key.map((k) => (
<span className={clsx('text', k)}>{k} - </span>
))}
</div>
);
}
ReactDOM.render(<App />, document.getElementById("react"));
.App {
font-family: sans-serif;
text-align: center;
}
.text{
font-size: 16px;
margin: 12px;
}
.todo {
color: orange;
}
.in-progress {
color: magenta;
}
.done {
color: green;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/17.0.1/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/17.0.1/umd/react-dom.production.min.js"></script>
<div id="react"></div>

unable to fetch data in nextjs headless WordPress

This is my header component --
import { isEmpty } from "lodash";
import Nav from "./Nav";
const Header = ({ headerMenus }) => {
// console.log(headerMenus); <- Here i get the menus
if (isEmpty(headerMenus)) {
return null;
}
return (
<Header>
<Nav headerMenus = {headerMenus}/>
</Header>
);
};
export default Header;
when I send data through props to nav I am not getting any data in nav component please help
here the code of Nav
const Nav = ({headerMenus}) =>{
// console.log(headerMenus); <~~ Not getting data here so i can't return anything through this
return;
};
export default Nav;
are you using WPGraphQL as your endpoint? you also more than likely need to have a bearer token unless your endpoint can be publicly introspected. Ill post a few files here from a current headless wordpress build I have
First, consider using something like apollo client to provide consistent caching globally. SWR and GraphQL Query are fantastic, too.
dynamic-nav.graphql
# import DynamicNavFragment from '../Partials/dynamic-nav-fields.graphql'
query DynamicNav(
$idHead: ID!
$idTypeHead: WordpressMenuNodeIdTypeEnum!
$idFoot: ID!
$idTypeFoot: WordpressMenuNodeIdTypeEnum!
) {
Header: wordpress {
menu(id: $idHead, idType: $idTypeHead) {
menuItems(where: { parentId: 0 }) {
edges {
node {
...DynamicNavFragment
childItems {
edges {
node {
...DynamicNavFragment
childItems {
edges {
node {
...DynamicNavFragment
}
}
}
}
}
}
}
}
}
}
}
Footer: wordpress {
menu(id: $idFoot, idType: $idTypeFoot) {
menuItems(where: { parentId: 0 }) {
edges {
node {
...DynamicNavFragment
childItems {
edges {
node {
...DynamicNavFragment
}
}
}
}
}
}
}
}
}
The fragment being used is
dynamic-nav-fields.graphql
fragment DynamicNavFragment on WordpressMenuItem {
id
label
path
parentId
}
It all comes together in a global layout.tsx component. There is a lengthy headless-nav component I'll include below but that's because it is 3 layers deep with dynamic subsubanchors
layout.tsx
import { FooterFixture } from './Footer';
import { HeadlessFooter } from './HeadlessFooter';
import { HeadlessNavbar } from './HeadlessNavbar';
import { Navbar } from './Navbar';
import { Meta } from './Meta';
import cn from 'classnames';
import {
DynamicNavQuery,
useDynamicNavQuery,
WordpressMenuNodeIdTypeEnum
} from '#/graphql/generated/graphql';
import {
ApolloError,
Button,
Fallback,
Modal,
LandingHero
} from '../UI';
import { useGlobal } from '../Context';
import { useAcceptCookies } from '#/lib/use-accept-cookies';
import Head from 'next/head';
import dynamic from 'next/dynamic';
import { useRouter } from 'next/router';
// import { LoginProps } from '../Auth/wp-login';
const dynamicProps = {
loading: () => <Fallback />
};
const LoginView = dynamic(
() => import('../Auth/wp-login'),
dynamicProps
);
const RegisterView = dynamic(
() => import('../Auth/wp-register'),
dynamicProps
);
const Featurebar = dynamic(
() => import('./Featurebar'),
dynamicProps
);
export interface LayoutProps {
Header: DynamicNavQuery['Header'];
Footer: DynamicNavQuery['Footer'];
className?: string;
title?: string;
hero?: React.ReactNode;
children?: React.ReactNode;
}
function AppLayout({
Header,
Footer,
className,
title,
children,
hero
}: LayoutProps) {
const { acceptedCookies, onAcceptCookies } = useAcceptCookies();
const { displayModal, modalView, closeModal } = useGlobal();
const { loading, error, data } = useDynamicNavQuery({
variables: {
idHead: 'Header',
idTypeHead: WordpressMenuNodeIdTypeEnum.NAME,
idTypeFoot: WordpressMenuNodeIdTypeEnum.NAME,
idFoot: 'Footer'
},
notifyOnNetworkStatusChange: true
});
const router = useRouter();
Header =
data != null && data.Header != null ? data.Header : undefined;
Footer =
data != null && data.Footer != null ? data.Footer : undefined;
return (
<>
<Head>
<title>{title ?? '✂ The Fade Room Inc. ✂'}</title>
</Head>
<Meta />
{error ? (
<>
<ApolloError error={error} />
</>
) : loading && !error ? (
<Fallback />
) : (
<Navbar
Desktop={<HeadlessNavbar header={Header} />}
Mobile={
<HeadlessNavbar
header={Header}
className={
'block px-3 py-2 rounded-md text-lg sm:text-base font-sans font-semibold text-secondary-0 hover:bg-redditSearch'
}
/>
}
/>
)}
<>
{router.pathname === '/' ? <LandingHero /> : hero}
<Modal open={displayModal} onClose={closeModal}>
{modalView === 'LOGIN_VIEW' && <LoginView />}
{modalView === 'SIGNUP_VIEW' && <RegisterView />}
</Modal>
<div className={cn('bg-redditSearch', className)}>
<main className='fit'>
{children}
{error ? (
<>
<ApolloError error={error} />
</>
) : loading && !error ? (
<Fallback />
) : (
<FooterFixture
children={<HeadlessFooter footer={Footer} />}
/>
)}
</main>
<div className='font-sans z-150'>
<Featurebar
title='This site uses cookies to improve your experience. By clicking, you agree to our Privacy Policy.'
hide={
acceptedCookies ? !!acceptedCookies : !acceptedCookies
}
className='prose-lg sm:prose-xl bg-opacity-90 sm:text-center'
action={
<Button
type='submit'
variant='slim'
className='mx-auto text-secondary-0 text-center rounded-xl border-secondary-0 border-1 hover:bg-gray-700 hover:bg-opacity-80 hover:border-secondary-0 duration-500 ease-in-out transform-gpu transition-colors'
onClick={() => onAcceptCookies()}
>
Accept Cookies
</Button>
}
/>
</div>
</div>
</>
</>
);
}
export default AppLayout;
The HeadlessNavbar component being injected with data from the useDynamicNavQuery function is mapped out here. I have been meaning to fractionate this apart but it is a bit of a pain.
Layout/HeadlessNavbar/headless-navbar.tsx
import { useState, FC, useRef, useEffect } from 'react';
import cn from 'classnames';
import { useRouter } from 'next/router';
import Link from 'next/link';
import { Button } from '../../UI';
import { Transition, Listbox } from '#headlessui/react';
import { NavArrow } from '#/components/UI/Icons';
import { DynamicNavQuery } from '#/graphql/generated/graphql';
import css from './headless-navbar.module.css';
import { DynamicNavSubAnchors } from '#/types/dynamic-nav';
import throttle from 'lodash.throttle';
export interface HeadlessNavbarProps
extends DynamicNavSubAnchors {
header: DynamicNavQuery['Header'];
className?: string;
}
const HeadlessNavbar: FC<HeadlessNavbarProps> = ({
header,
className,
node
}) => {
const [isOpen, setIsOpen] = useState(false);
const [subOpen, setSubOpen] = useState(false);
const [hasScrolled, setHasScrolled] = useState(false);
const [selectedCategory, setSelectedCategory] = useState(node);
const { pathname } = useRouter();
const isOpenRef = useRef(isOpen);
const refAcceptor =
useRef() as React.MutableRefObject<HTMLDivElement>;
useEffect(() => {
const handleScroll = throttle(() => {
const offset = window.scrollY ?? 0;
const { scrollTop } = document.documentElement;
const scrolled = scrollTop > offset;
setHasScrolled(scrolled);
setIsOpen(isOpenRef.current);
}, 200);
document.addEventListener('scroll', handleScroll);
return () => {
document.removeEventListener('scroll', handleScroll);
};
}, [hasScrolled, isOpenRef.current]);
return (
<>
{header?.menu?.menuItems?.edges &&
header.menu.menuItems.edges.length > 0 ? (
header.menu.menuItems.edges.map((top, i) => {
return top != null &&
top.node != null &&
top.node.label != null ? (
<>
{top.node.childItems?.edges &&
top.node.childItems.edges.length !== 0 ? (
<>
<Link
href={top.node.path}
as={top.node.path}
passHref
scroll={true}
key={top.node.id}
>
<a
id='top'
className={cn(className, {
[css.activeWithChildren]: pathname === top.node.path,
[css.linkWithChildren]: pathname !== top.node.path
})}
>
<p className='inline-flex'>{top.node.label}</p>
</a>
</Link>
<button
onClick={() => setIsOpen(!isOpen)}
id='sub-menu'
aria-haspopup={true}
aria-expanded={true}
type='button'
className={cn(css.topButton, {
'-rotate-180': isOpen,
'rotate-0': !isOpen
})}
>
<NavArrow className='select-none w-5 h-5' />
</button>
</>
) : top.node.childItems?.edges &&
top.node.childItems.edges.length < 1 ? (
<Link
href={top.node.path}
as={top.node.path}
passHref
scroll={true}
key={top.node.id}
>
<a
id='top'
className={cn(className, {
[css.active]: pathname === top.node.path,
[css.link]: pathname !== top.node.path
})}
>
<p>{top.node.label}</p>
</a>
</Link>
) : (
<></>
)}
{top.node.childItems != null &&
top.node.childItems.edges != null &&
top.node.childItems.edges.length > 0 ? (
<div className='lg:relative z-150 -ml-2'>
<Transition
show={isOpen}
enter='transition ease-out duration-200 '
enterFrom='transform opacity-0 translate-y-1'
enterTo='transform opacity-100 translate-y-0'
leave='transition ease-in duration-150'
leaveFrom='transform opacity-100 translate-y-0'
leaveTo='transform opacity-0 translate-y-1'
>
<div className={cn(css.transitionAlpha, '')}>
<div
className={css.transitionBeta}
ref={refAcceptor}
role='menu'
aria-orientation='vertical'
aria-labelledby='sub-menu'
>
<div className={css.transitionGamma}>
{top!.node!.childItems!.edges!.map((sub, j) => {
return sub != null &&
sub.node != null &&
sub.node.label != null &&
sub.node.parentId != null ? (
<Listbox
key={sub.node.path}
value={selectedCategory}
onChange={setSelectedCategory}
>
{({ open }) => (
<>
<div className={cn(css.divOpen)}>
<p className='text-base font-medium pr-2 bg-redditNav font-sans inline-flex py-2 ml-4'>
{sub!.node!.label!}
<Listbox.Button
as='button'
aria-haspopup={true}
id='sub'
aria-expanded={true}
onClick={() => setSubOpen(!subOpen)}
className={cn(css.bottomButton, {
' bg-redditNav -rotate-180': open,
' bg-redditNav rotate-0': !open
})}
>
<NavArrow className='select-none w-5 h-5' />
</Listbox.Button>
</p>
</div>
<Transition
show={open}
enter='transition ease-out duration-100'
enterFrom='transform opacity-0 scale-95'
enterTo='transform opacity-100 scale-100'
leave='transition ease-in duration-75'
leaveFrom='transform opacity-100 scale-100'
leaveTo='transform opacity-0 scale-95'
>
<Listbox.Options
static
className='outline-none select-none focus:outline-none'
>
{sub!.node!.childItems != null &&
sub!.node!.childItems.edges != null &&
sub!.node!.childItems.edges.length > 0 ? (
sub!.node!.childItems!.edges!.map(
(subsub, k) => {
return subsub != null &&
subsub.node != null &&
subsub.node.label != null &&
subsub.node.parentId != null ? (
<>
{open && (
<Listbox.Option
key={subsub.node.id}
className={cn(
css.subsub,
'lg:text-base text-sm hover:bg-redditSearch font-medium list-none outline-none font-sans'
)}
value={subsub!.node!.label}
>
<>
<Link
href={subsub!.node!.path}
passHref
key={k++}
shallow={true}
>
<a
id='subsub'
className={cn(css.subsubanchor)}
>
{subsub!.node!.label}
</a>
</Link>
</>
</Listbox.Option>
)}
</>
) : (
<></>
);
}
)
) : (
<></>
)}
</Listbox.Options>
</Transition>
</>
)}
</Listbox>
) : (
<></>
);
})}
</div>
</div>
</div>
</Transition>
</div>
) : (
<></>
)}
</>
) : (
<></>
);
})
) : (
<></>
)}
</>
);
};
export default HeadlessNavbar;
Here's the live site the code powers

Resources