I am somewhat new to using useEffect and useRef. What I'm trying to do is create a content management system that enables scrollIntoView for n number of page elements from a database for a single page scrolling app.
I'm not able to get it to work so I'm hoping for some assistance.
I've posted a simple test where I have a functional react component where section 2 works (clicking the button scrolls down to the page element.
But my goal is to take 'n' sections from a database and create 'n' number of refs to scroll down to 'n' number of sections.
I tries useRef, useEffect, etc but I'm stumped. My code snippet shows a working example with manual ref declarations for Section 2 but Section 1 attempts to use a collection of Refs and that's not working
Here is a code example: https://codesandbox.io/embed/trusting-stallman-bsjj1?fontsize=14
import React, { useRef } from "react";
import ReactDOM from "react-dom";
import "./styles.css";
const App = () => {
let pageRef = useRef([
React.createRef(),
React.createRef()
]);
const pageHomeRef = React.createRef();
const scrollToRef = ref => ref.current.scrollIntoView({ behavior: "smooth" });
const scrollToPane = ref => scrollToRef(ref);
return (
<div className="App">
<div className="menu">
<button
onClick={() => {
scrollToPane(pageRef[1]);
}}
>
Scroll to Section 1
</button>
<button onClick={() => scrollToPane(pageHomeRef)}>
Section 2
</button>
</div>
<div className="page" style={{ marginTop: "1500px", marginBottom: "1500px" }}>
<div className="section1" ref={pageRef[1]}>
Section 1
</div>
<div className="section2" ref={pageHomeRef}>
Section 2
</div>
</div>
</div>
);
};
I'd like to feed an array of page elements and have the refs dynamically change as needed.
Little order the code - try it
You can use React.createRef() or useRef(null) in the array.
And make us many refs you want in array.
And even make map if you have a list (https://dev.to/ajsharp/-an-array-of-react-refs-pnf)
And you have other ways insted of refs.
import React, { useRef } from "react";
import ReactDOM from "react-dom";
import "./styles.css";
const App = () => {
let pageRef = [useRef(null),useRef(null)];
const pageHomeRef = [React.createRef(),React.createRef()];
const scrollToRef = ref => ref.current.scrollIntoView({ behavior: "smooth" });
const scrollToPane = num => scrollToRef(pageRef[num]);
return (
<div className="App">
<div className="menu">
<button
onClick={() => {scrollToPane(0)}}>Scroll to Section 1</button>
<button onClick={() => scrollToPane(1)}>Section 2</button>
</div>
<div
className="page"
style={{ marginTop: "1500px", marginBottom: "1500px" }}
>
<div className="section1" ref={pageRef[0]}>
Section 1
</div>
<div className="section2" ref={pageRef[1]}>
Section 2
</div>
</div>
</div>
);
};
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
Related
I have data that I am pulling from ajax. And I want this data display only when it is successfully pulled.
import Alpine from 'alpinejs'
import axios from 'axios'
const http = axios.create({
baseURL: 'url',
})
window.axios = http
window.Alpine = Alpine
document.addEventListener('alpine:init', () => {
Alpine.data('cart', () => ({
items: null,
init(){
window.axios.get('wc/store/cart')
.then(({data})=>{
this.items = data
console.log(this.items)
}).catch(error => {
console.log(error.toString())
})
},
}))
})
Alpine.start()
Now I am using this in my template
<div x-data="cart">
<template x-if="items">
<h1>Shopping Cart</h1>
<!-- display items here -->
</template
</div>
The thing is, the h1 element is displayed but not the data from ajax.
Am I doing anything wrong. I am pretty confidence this should work.
You're not displaying your items. Keep in mind that template tags require a single root element only.
<div x-data="card">
<template x-if="items">
<div>
<h1>Shopping Cart</h1>
<template x-for="item in items">
<div>
<h2 x-text="item.text"></h2>
</div>
</template>
</div>
</template>
</div>
Goal:
When you press the button named "yes 1", the value should contain "yes yes" and in the end the console.log should display "test yes yes".
When you press the button named "yes 2", the value should contain "no no" and in the end the console.log should display "test no no".
The display of the value "test yes yes" or "test no no" take place at index.tsx.
The execution or the decision take place at ModalForm.tsx.
Problem:
In technical perspectiv, tried to find a solution by using this code onClick={props.onClose("yes yes")} but it doesn't work.
How do I solve this case?
Stackblitz:
https://stackblitz.com/edit/react-ts-rpltpq
Thank you!
index.html
<div id="root"></div>
<!-- Latest compiled and minified CSS -->
<link
href="https://cdn.jsdelivr.net/npm/bootstrap#5.1.3/dist/css/bootstrap.min.css"
rel="stylesheet"
/>
<!-- Latest compiled JavaScript -->
<script src="https://cdn.jsdelivr.net/npm/bootstrap#5.1.3/dist/js/bootstrap.bundle.min.js"></script>
index.tsx
import React, { Component, useState, useEffect } from 'react';
import { render } from 'react-dom';
import Hello from './Hello';
import { ModalForm } from './ModalForm';
import './style.css';
interface dddd {
clientid: string | undefined;
idid: number;
}
const getTest = () => {
console.log('test');
};
const App = () => {
const [clientiddd, setClientid] = useState('ddfdf');
const [idid, setIdid] = useState(0);
return (
<div>
<button
data-bs-toggle="modal"
data-bs-target="#myModalll"
className={'btn btn-outline-dark'}
>
{'data'}
</button>
<br />
<ModalForm clientid={clientiddd} onClose={getTest} />
</div>
);
};
render(<App />, document.getElementById('root'));
ModalForm.tsx
import React, { Component } from 'react';
interface ModalProps {
clientid: string | undefined;
onClose: () => void;
}
export const ModalForm = (props: ModalProps) => {
return (
<div
className="modal"
id="myModalll"
data-bs-backdrop="static"
data-bs-keyboard="false"
tabIndex={-1}
aria-labelledby="staticBackdropLabel"
aria-hidden="true"
>
<div className="modal-dialog">
<div className="modal-content">
<div className="modal-header">
<h4 className="modal-title">T</h4>
<button
type="button"
className="btn-close btn-close-black"
data-bs-dismiss="modal"
onClick={props.onClose}
></button>
</div>
<div className="modal-body">
TITLE:
<br />
<button
type="button"
data-bs-dismiss="modal"
onClick={props.onClose}
>
yes 1
</button>
<button
type="button"
data-bs-dismiss="modal"
onClick={props.onClose}
>
yes 2
</button>
<br />
</div>
</div>
</div>
</div>
);
};
It's somewhat hard to understand your question, but let me try.
onClick={props.onClose('yes yes')}
What this code does is that it calls props.onClick with yes yes as an argument and assigns the returned value as the onClick listener.
Assume the props.onClose is this:
function onClose() {
console.log('test')
}
What it does here is that it calls this function (it logs test to the console) but since this function is not returning anything, it passes undefined as the onClick here.
If instead your function was this:
function onClose(result) {
return function () {
console.log('test', result)
}
}
Now it would call props.onClose with yes yes and it would return a function. This anonymous function would be passed as the onClick event listener and when you click, it would call that so there would be test yes yes logged only after clicking.
You can as well do it differently, keep your onClose function as it was but introduce result:
function onClose(result) {
console.log('test', result)
}
but now you have to pass this function instead of calling it:
onClick={() => props.onClose('yes yes')}
As you can see, there will always be one anonymous function somewhere in there, it's just a question of where that function is and what is called when. Hope this explanation helps.
https://stackblitz.com/edit/react-ts-nw6upt?file=index.html
index.html
<div id="root"></div>
<!-- Latest compiled and minified CSS -->
<link
href="https://cdn.jsdelivr.net/npm/bootstrap#5.1.3/dist/css/bootstrap.min.css"
rel="stylesheet"
/>
<!-- Latest compiled JavaScript -->
<script src="https://cdn.jsdelivr.net/npm/bootstrap#5.1.3/dist/js/bootstrap.bundle.min.js"></script>
index.tsx
import React, { Component, useState, useEffect } from 'react';
import { render } from 'react-dom';
import Hello from './Hello';
import './style.css';
import { ModalForm } from './ModalForm';
interface dddd {
clientid: string | undefined;
idid: number;
}
const getTest = (result: string) => {
console.log('testff ' + result);
};
const App = () => {
const [clientiddd, setClientid] = useState('ddfdf');
const [idid, setIdid] = useState(0);
return (
<div>
<button
data-bs-toggle="modal"
data-bs-target="#myModalll"
className={'btn btn-outline-dark'}
>
{'data'}
</button>
<br />
<ModalForm clientid={clientiddd} onClose={getTest} />
</div>
);
};
render(<App />, document.getElementById('root'));
ModalForm.tsx
import React, { Component } from 'react';
interface ModalProps {
clientid: string | undefined;
onClose: (result: string) => void;
}
export const ModalForm = (props: ModalProps) => {
return (
<div
className="modal"
id="myModalll"
data-bs-backdrop="static"
data-bs-keyboard="false"
tabIndex={-1}
aria-labelledby="staticBackdropLabel"
aria-hidden="true"
>
<div className="modal-dialog">
<div className="modal-content">
<div className="modal-header">
<h4 className="modal-title">T</h4>
<button
type="button"
className="btn-close btn-close-black"
data-bs-dismiss="modal"
onClick={() => props.onClose('ccc')}
></button>
</div>
<div className="modal-body">
TITLE:
<br />
<button
type="button"
data-bs-dismiss="modal"
onClick={() => props.onClose('aaa')}
>
yes 1
</button>
<button
type="button"
data-bs-dismiss="modal"
onClick={() => props.onClose('bbb')}
>
yes 2
</button>
<br />
</div>
</div>
</div>
</div>
);
};
I'm trying to use a react modal to allow someone to change a value and have the modal buttons be dynamic such that initially there are Cancel / Submit buttons, but after Submit is pressed and the value is changed, the buttons are replaced with a Close button.
The problem I am having is that using "{modalButtonGroup}" in the modal results in "tmpName" being undefined when "handleSetNameSubmit" is called. If I instead comment out the "{modalButtonGroup}" line and just use hard coded buttons (which are currently commented out in the below code), then "tmpName" is set correctly when "handleSetNameSubmit" is called.
Is there some aspect of state context that causes "tmpName" to not be known when "{modalButtonGroup}" is used?
import { useState, useEffect } from 'react';
import { Row, Table, Form, Button, Modal, Alert } from 'react-bootstrap';
const System = () => {
const [tmpName, setTmpName] = useState();
const [showName, setShowName] = useState(false);
const handleClose = () => {
setShowName(false);
}
const handleCancel = () => {
setShowName(false);
};
const handleSetNameSubmit = () => {
console.log('tmpName: ', tmpName);
//code to change the name to tmpName
setModalButtonGroup(modalButtonsPostSubmit);
}
const modalButtonsPreSubmit = () => {
return (
<>
<Button variant="secondary" onClick={handleCancel}>
Cancel
</Button>
<Button variant="primary" onClick={handleSetNameSubmit}>
Submit
</Button>
</>
)
};
const modalButtonsPostSubmit = () => {
return (
<>
<Button variant="secondary" onClick={handleClose}>
Close
</Button>
</>
)
};
const [modalButtonGroup, setModalButtonGroup] = useState(modalButtonsPreSubmit);
return (
<>
<div className="card">
<h5>System</h5>
<Table variant="dark" responsive>
<tr>
<td>Name:</td>
<td>Name <Button onClick={() => setShowName(true)}>Edit</Button></td>
</tr>
</Table>
</div>
{/* Set Name */}
<Modal show={showName} onHide={handleClose}>
<Modal.Header closeButton>
<Modal.Title>Set Name</Modal.Title>
</Modal.Header>
<Modal.Body>
<span>
<Form.Control
type="text"
defaultValue=name
onChange={(event) => setTmpName(event.target.value)}
/>
</span>
</Modal.Body>
<Modal.Footer>
{modalButtonGroup}
{/*<Button variant="secondary" onClick={handleCancel}>*/}
{/* Cancel*/}
{/*</Button>*/}
{/*<Button variant="primary" onClick={handleSetNameSubmit}>*/}
{/* Submit*/}
{/*</Button>*/}
</Modal.Footer>
</Modal>
}
export default System;
UPDATE, I tried updating the code per suggestion as follows but now no buttons are appearing at all.
import { useState, useEffect } from 'react';
import { Row, Table, Form, Button, Modal, Alert } from 'react-bootstrap';
const System = () => {
const [tmpName, setTmpName] = useState();
const [showName, setShowName] = useState(false);
const [submitted, setSubmitted] = useState(false);
const handleClose = () => {
setShowName(false);
}
const handleCancel = () => {
setShowName(false);
};
const handleSetNameSubmit = () => {
console.log('tmpName: ', tmpName);
//code to change the name to tmpName
setSubmitted(modalButtonsPostSubmit);
}
const modalButtonsPreSubmit = () => {
return (
<>
<Button variant="secondary" onClick={handleCancel}>
Cancel
</Button>
<Button variant="primary" onClick={handleSetNameSubmit}>
Submit
</Button>
</>
)
};
const modalButtonsPostSubmit = () => {
return (
<>
<Button variant="secondary" onClick={handleClose}>
Close
</Button>
</>
)
};
const buttons = submitted ? modalButtonsPostSubmit : modalButtonsPreSubmit;
return (
<>
<div className="card">
<h5>System</h5>
<Table variant="dark" responsive>
<tr>
<td>Name:</td>
<td>Name <Button onClick={() => setShowName(true)}>Edit</Button></td>
</tr>
</Table>
</div>
{/* Set Name */}
<Modal show={showName} onHide={handleClose}>
<Modal.Header closeButton>
<Modal.Title>Set Name</Modal.Title>
</Modal.Header>
<Modal.Body>
<span>
<Form.Control
type="text"
defaultValue=name
onChange={(event) => setTmpName(event.target.value)}
/>
</span>
</Modal.Body>
<Modal.Footer>
{buttons}
{/*<Button variant="secondary" onClick={handleCancel}>*/}
{/* Cancel*/}
{/*</Button>*/}
{/*<Button variant="primary" onClick={handleSetNameSubmit}>*/}
{/* Submit*/}
{/*</Button>*/}
</Modal.Footer>
</Modal>
}
export default System;
Here's what's happening. It's kind of complicated because of the unusual way in which you have written your component. I'll suggest a simpler way to do it below, but it might be educational to unpack what's going on.
Your <System> component renders for the first time:
tmpName is undefined
the handleNameSubmit function is generated and it "closes over" the current value of tmpName. This means every time this particular function value is called, it will always console.log 'tmpName: undefined'. See background on JavaScript closures
the modalButtonsPreSubmit function is generated and it closes over the current value of handleNameSubmit and binds this value to the submit button click event.
Then, you pass the modalButtonsPreSubmit function as the initial value of a useState hook. The way that useState works, this initial value is only used in the first render (see docs). The modalButtonsGroup value returned by this useState call will be frozen to this particular value (with all the closures) through subsequent re-renders, until you change it by calling setModalButtonsPreSubmit with a new function.
The user types some text in the textbox. For each character your onChange handler calls setTempName, which triggers the <System> component to re-render with a new value in the tmpName state. However, modalButtonsPreSubmit is still frozen to what it was in the first render.
The user clicks "Submit", which triggers the version handleNameSubmit that was generated on the first render, when tmpName was undefined.
The way to simplify things so that it works as expected is to not store functions in state. That way they'll get re-generated on each re-render with fresh values for any other state that they reference.
So instead of..
const modalButtonsPreSubmit = () => (
<> {/* Markdown for "Submit" and "Cancel" buttons */} </>
);
const modalButtonsPostSubmit = () => (
<> {/* Markdown for "Close" button */} </>
);
const [modalButtonGroup, setModalButtonGroup] = useState(modalButtonsPreSubmit);
return (
<div>
{/* The rest of the app */}
{modalButtonGroup}
</div>
);
You'd do something like this...
const [submitted, setSubmitted] = useState(false);
const buttons = submitted ?
<> {/* Markdown for "Close" button */} </> :
<> {/* Markdown for "Submit" and "Cancel" buttons */} </>;
return (
<div>
{/* The rest of the app */}
{buttons}
</div>
);
See this codesandbox for a working solution.
All my other pages work fine. And this happened after I made an addition to App.js, which I subsequently removed. Obviously I must have made some other change, I'm not able to find it however.
Error: React.Children.only expected to receive a single React element child.
▶ 18 stack frames were collapsed.
(anonymous function)
src/components/Feed.js:16
13 | .then(res => res.json())
14 | .then((data) => {
15 |
> 16 | this.setState({ events: data })
| ^ 17 |
18 | })
19 | .catch(console.log)
Looking at Feed.js I've not modified anything here.
import React, {Component} from "react";
import Header from "./Header"
import Events from "./Events"
class Feed extends Component {
state = {
events: []
}
componentDidMount() {
fetch('http://127.0.0.1:5002/events')
.then(res => res.json())
.then((data) => {
this.setState({ events: data })
})
.catch(console.log)
}
render() {
return (
<div className="feed">
<Header />
<div class="container">
<Events events={this.state.events} />
</div>
</div>
);
}
}
export default Feed;
And the only other component it calls is as below:
import React from 'react';
import Accordion from 'react-bootstrap/Accordion'
import Card from 'react-bootstrap/Card'
import Alert from 'react-bootstrap/Alert'
import Button from 'react-bootstrap/Button'
import './App.css'
function Events ({events}){
return (
<div>
<center><h1>Event List</h1></center>
{events.map((event) => (
<div class="mt-2">
<Accordion defaultActiveKey="0" className="rounded ">
<Card className="rounded">
<Accordion.Toggle as={Alert} variant="secondary" eventKey={event.id} className="accordion-title border-0"><span className="badge badge-secondary">{event.date}</span><span> </span><span>{event.title}</span>
</Accordion.Toggle>
<Accordion.Collapse eventKey={event.id} className="accordion-collapse">
<Button href="detail" variant="secondary" size="lg" disabled>Link</Button>
<Card.Body className="rounded">{event.summary}</Card.Body>
</Accordion.Collapse>
</Card>
</Accordion>
</div>
))}
</div>
)
}
export default Events;
As I said I've not modified anything in these two files and I've made sure to remove any changes to other files.
I did make a change which caused the issue.
In Events.js I added a new button to the accordion. On removing this everything worked fine.
The offending entry
<Button href="detail" variant="secondary" size="lg" disabled>Link</Button>
The Button should have been inside the Card.Body.
<Accordion defaultActiveKey="0" className="rounded ">
<Card className="rounded">
<Accordion.Toggle as={Alert} variant="secondary" eventKey={event.id} className="accordion-title border-0"><span className="badge badge-secondary">{event.date}</span><span> </span><span>{event.title}</span>
</Accordion.Toggle>
<Accordion.Collapse eventKey={event.id} className="accordion-collapse">
<Card.Body className="rounded">{event.summary}
<Button href="detail" variant="secondary" size="lg" disabled>Link</Button>
</Card.Body>
</Accordion.Collapse>
</Card>
</Accordion>```
I am working on reactjs 5.X version. I am writing UT with enzyme. Below is my reusable component with prop arguments. Not able to find the div tags inside of the const component.Can you help me how to get the div tags using classname or using find function to get div tags.
import React from 'react';
import styles from './democheck.css';
const democheck = ({ input, cb,
required, testCheckbox, checked, label, meta: { error } }) => (
<div id="hierrdivid" className={styles.testDivColumn}>
<div className={styles.testDiv}>
<br />
<span className={styles.testLabel}>
{label}
</span>
{required && <span className={styles.error}>
*
</span>}
<input
{...input}
name={`form-field-${input.name}`}
checked={checked}
id={input.name}
className={testCheckbox ? styles.testCheckboxError :
styles.testCheckbox}
type="checkbox"
onChange={() => {
if (cb) {
cb(document.getElementById(input.name).checked);
}
}}
/>
</div>
<div className={styles.testerrorDiv}>
{testCheckbox &&
<div className={styles.testerrorLabel}>
{label} {error}
</div>}
</div>
</div>
);
democheck.propTypes = {
input: React.PropTypes.objectOf(React.PropTypes.oneOfType([
React.PropTypes.string,
React.PropTypes.func
])),
cb: React.PropTypes.func,
label: React.PropTypes.string,
meta: React.PropTypes.shape({}),`enter code here`
required: React.PropTypes.bool,
checked: React.PropTypes.bool,`enter code here`
testCheckbox: React.PropTypes.bool
};