I am having an issue where I have an element, but I cannot seem to reference it without getting a null error.
render() {
const getXandY = () => {
const elment = document.getElementById("test");
const xpos = elment.style.left;
const ypos = elment.style.top;
console.log(xpos);
console.log(ypos);
};
return (
getXandY(),
(
<div id="test" className="treeContainer">
{this.state.configs.map((config) => (
<div>
<Configs configName={config.name} configNumber={config.id} />
</div>
))}
</div>
)
);
}
}
any help is appreciated.
I'm not sue you understand React. In render() function try to avoid defining functions, it's a function for just returning the JSX you want to render (you can't return nothing else there). So define your getXandY outside render(). If you really want to invoke it on rendering, simply put the function call inside, that's it. So it would look something like:
const getXandY = ...
render() {
getXandY();
return (
<div ... />
);
{
Related
I've been writing a chess application in order to help myself get up to speed on hooks introduced in React. I've got two main components so far; one for the board itself and one for a move history that allows you to revert back to a previous move. When I try to use a callback in the Board component to pass a move to the move history, I get an error Cannot update a component ('App') while rendering a different component ('MoveHistory'). I understand the error but I'm not fully sure what I'm supposed to do about it.
My components (minus all the parts I'm pretty sure are irrelevant) are as follows:
App.tsx (parent component)
...
const STARTING_FEN = 'rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1'
function App() {
const [FEN, setFEN] = useState(STARTING_FEN);
const [moveHistory, setMoveHistory] = useState<string[]>([]);
const [fenHistory, setFenHistory] = useState<string[]>([]);
// rewind game state to specified move index
function onRewind(target: number) {
setFEN(fenHistory[target]);
setMoveHistory(moveHistory.slice(0, target));
setFenHistory(fenHistory.slice(0, target));
}
// add a new move to the move history
function onMove(move: string, FEN: string) {
setMoveHistory([...moveHistory, move]);
setFenHistory([...fenHistory, FEN]);
}
return (
<div className='app'>
<Board FEN={FEN} onMove={onMove} />
<MoveHistory moves={moveHistory} onRewind={onRewind} />
</div>
);
}
export default App;
Board.tsx (sibling component 1)
...
interface BoardProps {
FEN: string;
onMove: Function;
}
function Board(props: BoardProps) {
const splitFEN = props.FEN.split(' ');
const [squares, setSquares] = useState(generateSquares(splitFEN[0]));
const [lastClickedIndex, setLastClickedIndex] = useState(-1);
const [activeColor, setActiveColor] = useState(getActiveColor(splitFEN[1]));
const [castleRights, setCastleRights] = useState(getCastleRights(splitFEN[2]));
const [enPassantTarget, setEnPassantTarget] = useState(getEnPassantTarget(splitFEN[3]));
const [halfMoves, setHalfMoves] = useState(parseInt(splitFEN[4]));
const [fullMoves, setFullMoves] = useState(parseInt(splitFEN[5]));
...
// handle piece movement (called when a user clicks on a square)
function onSquareClicked(index: number) {
... // logic determining where to move the piece
{
props.onMove(moveName, getFEN())
}
}
...
// get the FEN string for the current board
function getFEN(): string {
... //logic converting board state to strings
return `${pieceString} ${activeString} ${castleString} ${enPassantString} ${halfMoves} ${fullMoves}`;
}
return (
<div className='board'>
{[...Array(BOARD_SIZE)].map((e, rank) => {
return (
<div key={rank} className='row'>
{squares.slice(rank * BOARD_SIZE, BOARD_SIZE + rank * BOARD_SIZE).map((square, file) => {
return (
<Square
key={file}
index={coordsToIndex(rank, file)}
pieceColor={square.pieceColor}
pieceType={square.pieceType}
style={square.style}
onClick={onSquareClicked}
/>
);
})}
</div>
)
})};
</div>
);
}
export default Board;
MoveHistory.tsx (sibling component #2)
...
interface MoveHistoryProps {
moves: string[],
onRewind: Function;
}
function MoveHistory(props: MoveHistoryProps) {
return (
<div className='move-history'>
<div className='header'>
Moves
</div>
<div className='move-list'>
{_.chunk(props.moves, 2).map((movePair: string[], index: number) => {
return (
<div className='move-pair' key={index}>
<span>{`${index + 1}.`}</span>
<span onClick={props.onRewind(index * 2)}>{movePair[0]}</span>
<span onClick={props.onRewind(index * 2 + 1)}>{movePair[1] || ""}</span>
</div>
)
})}
</div>
</div>
)
}
export default MoveHistory;
I've looked at a bunch of other stackoverflow questions that seem to answer the question I'm asking here but to me it looks like I'm already doing what's recommended there, so I'm not sure what the difference is. I've also seen some recommendations to use Redux for this, which I'm not opposed to, but if it can be avoided that would be nice.
The issue is that you are calling props.onRewind in the render of your MoveHistory. This is effectively what happens:
App starts rendering
MoveHistory starts rendering, and calls onRewind
Within onRewind, you call various useState setter methods within App. App hasn't finished rendering yet, but it's state-modifying methods are being called. This is what triggers the error.
I think you mean to do something like this instead:
...
interface MoveHistoryProps {
moves: string[],
onRewind: Function;
}
function MoveHistory(props: MoveHistoryProps) {
return (
<div className='move-history'>
<div className='header'>
Moves
</div>
<div className='move-list'>
{_.chunk(props.moves, 2).map((movePair: string[], index: number) => {
return (
<div className='move-pair' key={index}>
<span>{`${index + 1}.`}</span>
<span onClick={() => props.onRewind(index * 2)}>{movePair[0]}</span>
<span onClick={() => props.onRewind(index * 2 + 1)}>{movePair[1] || ""}</span>
</div>
)
})}
</div>
</div>
)
}
export default MoveHistory;
Note that, instead of calling props.onRewind you are giving it a method which, when the span is clicked, will call onRewind.
I have a state and I want use map to go through it,
function App() {
const [cardlayouts,setCardLayouts]=useState({id:"1",title:"title1",img:{pic1},text:"text1"})
return (
<div className="App">
{
cardlayouts.map(s=><CardLayout id={s.id} title={s.title} img={s.img} text={s.text}/>)
}
</div>
);
}
I get an error which says cardlayouts.map is not a function,any idea?
This is because you are trying map on the object. Your cardlayouts is object. You can use map only on array
Edit your code like this. To map you should have an array. But you have given a object instead.
const [cardlayouts, setCardLayouts] = useState([{id:"1",title:"title1",img:
{pic1},text:"text1"}])
.map() is an array method, but your state isn't an array.
You don't need to use map in this situation. Just access the keys like normal:
function App() {
const [cardlayouts,setCardLayouts] = useState({
id:"1",
title:"title1",
img:{pic1},
text:"text1"
})
return (
<div className="App">
<CardLayout
id={cardlayouts.id}
title={cardlayoust.title}
img={card.img}
text={s.text}
/>
</div>
);
}
Or, make the state an array and map over it like this:
function App() {
const [cardlayouts,setCardLayouts] = useState([
{
id:"1",
title:"title1",
img:{pic1},
text:"text1"
}
])
return (
<div className="App">
{cardlayouts.map(layout => (
<CardLayout
id={layout.id}
title={layout.title}
img={layout.img}
text={layout.text}
/>
))}
</div>
);
}
I'm trying to create a helper function that nest a passed element/component as a children of itself of several depths.
So something like this
const Chain = nested(4, () => <div />)
return (
<Chain />
)
Should render divs nested 4 levels
<div>
<div>
<div>
<div />
</div>
</div>
</div>
I implemented the function in React like this.
/** nest the given element in itself of specified depth */
export const nested = (
depth: number,
element: () => JSX.Element
): FC<any> => () => {
let chain = element();
for (let i = 0; i < depth; i++) {
chain = React.createElement(
element().type,
element().props,
chain
);
}
return chain;
};
It renders and displays the nested Divs correctly, but gives back an error message i don't understand how to correct.
Warning: Functions are not valid as a React child. This may happen if you return a Component instead of <Component /> from render. Or maybe you meant to call this function rather than return it.
Try going with a recursive solution.
const createInnerElements = (count, childElement) => {
if (count == 1) {
return <div>{childElement}</div>
}
return createInnerElements(count - 1, <div>{childElement}</div>);
}
// in your parent component,
return <>{createInnerElements(4, <></>)}</>
Here's the solution I settled with. Credit to #senthil balaji's answer.
export const NestedTags = (depth: number, childElement: JSX.Element): JSX.Element => {
if (depth === 1) {
return (
<childElement.type {...childElement.props}>
{childElement}
</childElement.type>
);
}
return NestedTags(
depth - 1,
<childElement.type {...childElement.props}>
{childElement}
</childElement.type>
);
};
and should be wrapped in a React Fragment using used in parent.
<>
{NestedTags(5, <div className='segment'><div>)}
</>
The caveat for this is that it can't nest when passed a React Component Type like so:
const Test = ({className}: {className: string}) => (
<a className={className}>hello</a>;
)
//...
return (
<>
{NestedTags(5, <Test className='test'/>}
</>
)};
I heard that () is not good at the rendering performance so it should not be used. However, in this case, it can not get the results when passing the function without ().
You can check the reseult in CodeSendbox: Link
const comp = () => {
return <div>This is Component</div>;
};
const elements = [comp(), comp]; // Please Attention This Line!
const items = [];
for (const [index, value] of elements.entries()) {
items.push(
<div>
{index}: {value} <br />
</div>
);
}
export default function Index() {
return <>{items}</>;
}
Result is:
0:
This is Component
1:
Why only comp() but not comp? What is the best practice in this case?
You can use JSX instead of calling the function:
const comp = () => {
return <div>This is Component</div>;
};
const elements = [comp, comp]; // Remove the function call, save only the reference
const items = [];
// need to use Value with V instead of v for JSX
for (const [index, Value] of elements.entries()) {
items.push(
<div>
{index}: <Value/> <br /> // render as JSX
</div>
);
}
export default function Index() {
return <>{items}</>;
}
Here's a sandbox link showing how it works: https://codesandbox.io/s/cranky-mclean-7ymzr
comp is an arrow function that returns a JSX literal. It's a "function" that is stored in a variable called comp. When called/invoked as comp(), notice the parenthesis, it is executed and the computed return value is returned in the array. When used without the parenthesis, like comp, this is simply the reference to the variable (that can be invoked like a function).
Functions aren't valid react children nor valid JSX.
Introducing JSX
const comp = () => {
return <div>This is Component</div>;
};
const elements = [comp(), comp]; // saves a JSX literal, and a function
const items = [];
for (const [index, value] of elements.entries()) {
items.push(
<div>
{index}: {value} <br /> // trying to render a function here in `value`
</div>
);
}
export default function Index() {
return <>{items}</>;
}
I assume you got the idea () is "not good at rendering performance" from some documentation about in-line anonymous arrow functions and event handlers and mapping an array of data. That is a different issue altogether. I believe your situation is just a misunderstanding about how arrow functions are invoked.
I currently have a react project I'm working on. My render method looks like this going into my return method:
render() {
let elements = [];
this.dropdownCounter().forEach(item => {
if(item != "attributeProduct") {
console.log('setting');
elements.push(
<Dropdown
title={this.state[item][0]['title']}
arrayId={item}
list={this.state[item]}
resetThenSet={this.resetThenSet}
/>
);
}
});
this.state.attributeProduct.map(attributeItem => {
elements.push(
<Dropdown
title={attributeItem.name}
arrayId='attributeMetaProduct'
list={
this.state.attributeMetaProduct.filter(metaItem => metaItem.attribute_id == attributeItem.ID)
}
resetThenSet={this.resetThenSet}
/>
);
});
return (
I have a lot of code going on in the render area due to different drop downs dependent on other methods. Is there a way that I can do something like this instead?
render() {
allMyPrereturnStuff()
return()
}
Then just place all this code in allMyPrereturnStuff()? I've tried creating this function and passing everything there but it doesn't work due to all the "this". Any ideas?
Yes, you can easily drop in normal javascript expressions into JSX:
return (
<div>
{this.renderStuff()}
{this.renderOtherStuff()}
{this.renderMoreStuff()}
</div>
);
You can even base it on flags:
const shouldRenderMoreStuff = this.shouldRenderMoreStuff();
return (
<div>
{this.renderStuff()}
{this.renderOtherStuff()}
{shouldRenderMoreStuff ? this.renderMoreStuff() : null}
</div>
);
Do note that it is often an anti-pattern to have render* methods in your components other than the normal render method. Instead, each render* method should probably be its own component.
Don't forget to bind your allMyPrereturnStuff() method in the constructor so "this" will work inside it.
constructor(props) {
super(props);
// ... your existing code
this.allMyPrereturnStuff = this.allMyPrereturnStuff.bind(this);
}
allMyPrereturnStuff = (params) => {
// ... all the code
}
However, you might want to consider breaking out the code to components, which is more Reacty way to do things.
For example, you could refactor this
this.state.attributeProduct.map(attributeItem => {
elements.push(<Dropdown
title={attributeItem.name}
arrayId='attributeMetaProduct'
list={
this.state.attributeMetaProduct.filter(metaItem => metaItem.attribute_id == attributeItem.ID)
}
resetThenSet={this.resetThenSet}
/>);
});
To something like (somewhat pseudocody):
const DropdownList = (props) => {
return (<Dropdown
title={props.attributeItem.name}
arrayId='attributeMetaProduct'
list={props.list}
resetThenSet={props.resetThenSet}
/>);
}
And in the original component's render function, have something like
render() {
return (this.state.attributeProduct.map(attributeItem => {
<DropdownList attributeItem={attributeItem}
list={ this.state.attributeMetaProduct.filter(metaItem => metaItem.attribute_id == attributeItem.ID) }
resetThenSet={this.resetThenSet}
/>);
}