React: Dynamic list of Refs with conditional render currently Null - javascript

I have a list of items that are conditionally rendered onto my page- I'd like each of them to have a unique ref based on their ID.
I tried creating a ref for each item within the constructor, but it seems either because the refs are created before the render, or because of my own error- each ref is set to null.
class EmailList extends Component {
constructor(props) {
super(props)
this.state={
emailreports: []
}
props.emailreports.forEach(thing => {
this[`${thing.id}_ref`] = React.createRef()
});
}
componentDidMount(){
this.setState({
emailreports: this.props.emailreports
})
}
render() {
const { emailreports } = this.state;
return (
<div>
<section>
<h5>Email Reports</h5>
<ul>
{
emailreports.length > 0
?
<>
{
emailreports.map((report, i) => {
return <EmailReportItem ref={this[`${report.id}_ref`]} />
})
}
</>
:
<section >
<div className="text-center">You do not currently have access to email report data.</div>
</section>
}
</ul>
</section>
</div>
)
}
}
export default connect(mapStateToProps)(EmailList);
The following image is the result of the above load:

Related

unable to find a React.Component by id

I have a React.Component with render() declared this way:
render(){
return <div>
<button id="butt" onClick={()=> $("#noti").change("test") }>click me</button>
<Notification id="noti" onMounted={() => console.log("test")}/>
</div>
}
And this is my Notification class:
class Notification extends React.Component {
constructor(props) {
super(props)
this.state = {
message: "place holder",
visible: false
}
}
show(message, duration){
console.log("show")
this.setState({visible: true, message})
setTimeout(() => {
this.setState({visible: false})
}, duration)
}
change(message){
this.setState({message})
}
render() {
const {visible, message} = this.state
return <div>
{visible ? message : ""}
</div>
}
}
As the class name suggests, I am trying to create a simple notification with message. And I want to simply display the notification by calling noti.show(message, duration).
However, when I try to find noti by doing window.noti, $("#noti") and document.findElementById("noti"), they all give me undefined, while noti is displayed properly. And I can find the butt using the code to find noti.
How should I find the noti? I am new to front end so please be a little bit more specific on explaining.
It's not a good idea using JQuery library with Reactjs. instead you can find a appropriate react library for notification or anything else.
Also In React we use ref to to access DOM nodes.
Something like this:
constructor(props) {
super(props);
this.noti = React.createRef();
}
...
<Notification ref={this.noti} onMounted={() => console.log("test")}/>
more info: https://reactjs.org/docs/refs-and-the-dom.html
I have hardcoded the id to 'noti' in the render method. You can also use the prop id in the Notification component.I have remodelled the component so that you can achieve the intended functionality through React way.
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
messageContent: 'placeholder'
}
}
setMessage = (data) => {
this.setState({messageContent : data});
}
render() {
return (
<div className="App">
<button id='butt' onClick= {() => this.setMessage('test')} />
<Notification message = {this.state.messageContent} />
</div>
);
}
}
class Notification extends React.Component {
render () {
const {message} = this.props;
return (
<div id='noti'>
{message}
</div>
)
}
}
Before beginning: Using id/class to reach DOM nodes is not suggested in React.js, you need to use Ref's. Read more at here.
In your first render method, you give id property to Notification component.
In react.js,
if you pass a property to some component, it becomes a props of that
component. (read more here)
After you give the id to Notification, you need to take and use that specific props in your Notification component.
You see that you inserted a code line super(props) in constructor of Notification? That means, take all the props from super (upper) class and inherit them in this class.
Since id is HTML tag, you can use it like:
class Notification extends React.Component {
constructor(props) {
// inherit all props from upper class
super(props);
this.state = {
message: "place holder",
visible: false,
// you can reach all props with using this.props
// we took id props and assign it to some property in component state
id: this.props.id
}
}
show(message, duration){
// code..
}
change(message){
// code..
}
render() {
const {visible, message, id} = this.state
// give that id to div tag
return <div id={id}>
{message}
</div>
}
}
You can't pass id/class to a React Component as you would declare them in your normal HTML. any property when passed to a React Component becomes a props of that component which you have to use in the component class/function.
render() {
const {visible, message} = this.state
// give your id props to div tag as id attr
return <div id={this.props.id}>
{message}
</div>
}
This answer does not provide the exact answer about selecting a component as you want. I'm providing this answer so you can see other alternatives (more React way maybe) and improve it according to your needs.
class App extends React.Component {
state = {
isNotiVisible: false
};
handleClick = () => this.setState({ isNotiVisible: true });
render() {
return (
<div>
<button onClick={this.handleClick}>Show Noti</button>
{this.state.isNotiVisible && (
<Noti duration={2000} message="This is a simple notification." />
)}
</div>
);
}
}
class Noti extends React.Component {
state = {
visible: true
};
componentDidMount() {
setTimeout(() => this.setState({ visible: false }), this.props.duration);
}
render() {
return this.state.visible && <div>{this.props.message}</div>;
}
}
ReactDOM.render(<App />, document.getElementById("root"));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
<div id="root" />

React Component receive props but doesn't render it, why?

I have a page displaying user's books.
On this MyBooks page, React component mount. When it's mounted it fetch user's books through API. Then it update component's state with user's books.
mount component
fetch books through API
when we have results, update component's state
render again BooksList component (but it's not happening)
Here is my code for MyBooks component :
class MyBooks extends Component {
// TODO: fetch user info
constructor(props) {
super(props);
this.state = {
books: [],
errors: []
};
this.fetchBooks = this.fetchBooks.bind(this);
}
componentDidMount() {
console.log('component mounted!');
this.fetchBooks();
}
fetchBooks() {
let _this = this;
BooksLibraryApi.getBooks().then(foundBooks => {
console.log('books found:', foundBooks);
_this.setState({
books: foundBooks
});
});
}
render() {
console.log('MyBooks state:', this.state);
return (
<Section>
<Container>
<h1>My books</h1>
<BooksList books={this.state.books} />
</Container>
</Section>
);
}
}
export default withRouter(MyBooks);
Here is the result for console.log('books found:', foundBooks):
Here is my code for BooksList component :
class BooksList extends React.Component {
render() {
console.log('BooksList props:', this.props);
return (
<Columns breakpoint="mobile">
{this.props.books.map((book, i) => {
console.log(book);
return (
<Columns.Column
key={i}
mobile={{ size: 'half' }}
desktop={{ size: 2 }}
>
<BookCard book={book} />
</Columns.Column>
);
})}
</Columns>
);
}
}
export default BooksList;
Here is the code for BookCard component:
class BookCard extends React.Component {
constructor(props) {
super(props);
console.log('props', props);
this.readBook = this.readBook.bind(this);
this.addBook = this.addBook.bind(this);
this.deleteBook = this.deleteBook.bind(this);
this.wantBook = this.wantBook.bind(this);
}
readBook() {
BooksLibraryApi.readBook(this.props.book.id);
}
addBook() {
BooksLibraryApi.addBook(this.props.book.id);
}
wantBook() {
BooksLibraryApi.wantBook(this.props.book.id);
}
deleteBook(e) {
BooksLibraryApi.deleteBook(this.props.book.id, e);
}
render() {
return (
<div className="card-book">
<Link to={`/book/${this.props.book.id}`}>
{this.props.book.doHaveThumbnail ? (
<Image
alt="Cover"
src={this.props.book.thumbnailUrl}
size={'2by3'}
/>
) : (
<div className="placeholder">
<span>{this.props.book.title}</span>
</div>
)}
</Link>
<Button fullwidth color="primary" size="small" onClick={this.wantBook}>
Add to wishlist
</Button>
</div>
);
}
}
export default withRouter(BookCard);
The console.log in BooksList component is not called. Which means that the component is render only one time, when the this.props.books array is empty.
I don't understand why BooksList is not rendered again when his props are updated (when MyBooks component has his state updated).
Strange behavior: I'm using React Router, and when I first click on the link "My books" (which go to my MyBooks component), it doesn't work, but when I click again on it, everything works fine. Which means that something is wrong with rendering / component's lifecyles.
Thanks.

ReactJS instant Search with input

Im making my first react project. Im new in JS, HTML, CSS and even web app programming.
What i want to do it is a Search input label. Now its look like this:
Like you can see i have some list of objects and text input.
I Have two components, my ProjectList.js with Search.js component...
class ProjectsList extends Component {
render() {
return (
<div>
<Search projects={this.props.projects} />
<ListGroup>
{this.props.projects.map(project => {
return <Project project={project} key={project.id} />;
})}
</ListGroup>
</div>
);
}
}
export default ProjectsList;
... and ProjectList.js displays Project.js:
How looks Search.js (its not ended component)
class Search extends Component {
state = {
query: ""
};
handleInputChange = () => {
this.setState({
query: this.search.value
});
};
render() {
return (
<form>
<input
ref={input => (this.search = input)}
onChange={this.handleInputChange}
/>
<p />
</form>
);
}
}
export default Search;
My project have name property. Could you tell me how to code Search.js component poperly, to change displaying projects dynamically based on input in text label? for example, return Project only, if text from input match (i want to search it dynamically, when i start typing m... it shows all projects started on m etc).
How to make that Search input properly? How to make it to be universal, for example to Search in another list of objects? And how to get input from Search back to Parent component?
For now, in react dev tools whatever i type there i get length: 0
Thanks for any advices!
EDIT:
If needed, my Project.js component:
class Project extends Component {
state = {
showDetails: false
};
constructor(props) {
super(props);
this.state = {
showDetails: false
};
}
toggleShowProjects = () => {
this.setState(prevState => ({
showDetails: !prevState.showDetails
}));
};
render() {
return (
<ButtonToolbar>
<ListGroupItem className="spread">
{this.props.project.name}
</ListGroupItem>
<Button onClick={this.toggleShowProjects} bsStyle="primary">
Details
</Button>
{this.state.showDetails && (
<ProjectDetails project={this.props.project} />
)}
</ButtonToolbar>
);
}
}
export default Project;
To create a "generic" search box, perhaps you could do something like the following:
class Search extends React.Component {
componentDidMount() {
const { projects, filterProject, onUpdateProjects } = this.props;
onUpdateProjects(projects);
}
handleInputChange = (event) => {
const query = event.currentTarget.value;
const { projects, filterProject, onUpdateProjects } = this.props;
const filteredProjects = projects.filter(project => !query || filterProject(query, project));
onUpdateProjects(filteredProjects);
};
render() {
return (
<form>
<input onChange={this.handleInputChange} />
</form>
);
}
}
This revised version of Search takes some additional props which allows it to be reused as required. In addition to the projects prop, you also pass filterProject and onUpdateProjects callbacks which are provided by calling code. The filterProject callback allows you to provide custom filtering logic for each <Search/> component rendered. The onUpdateProjects callback basically returns the "filtered list" of projects, suitable for rendering in the parent component (ie <ProjectList/>).
The only other significant change here is the addition of visibleProjects to the state of <ProjectList/> which tracks the visible (ie filtered) projects from the original list of projects passed to <ProjectList/>:
class Project extends React.Component {
render() {
return (
<div>{ this.props.project }</div>
);
}
}
class ProjectsList extends React.Component {
componentWillMount() {
this.setState({ visibleProjects : [] })
}
render() {
return (
<div>
<Search projects={this.props.projects} filterProject={ (query,project) => (project == query) } onUpdateProjects={ projects => this.setState({ visibleProjects : projects }) } />
<div>
{this.state.visibleProjects.map(project => {
return <Project project={project} key={project.id} />;
})}
</div>
</div>
);
}
}
class Search extends React.Component {
componentDidMount() {
const { projects, filterProject, onUpdateProjects } = this.props;
onUpdateProjects(projects);
}
handleInputChange = (event) => {
const query = event.currentTarget.value;
const { projects, filterProject, onUpdateProjects } = this.props;
const filteredProjects = projects.filter(project => !query || filterProject(query, project));
onUpdateProjects(filteredProjects);
};
render() {
return (
<form>
<input onChange={this.handleInputChange} />
</form>
);
}
}
ReactDOM.render(
<ProjectsList projects={[0,1,2,3]} />,
document.getElementById('react')
);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.0.0/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.0.0/umd/react-dom.production.min.js"></script>
<div id="react"></div>
I will assumes both your Search and ProjectList component have a common parent that contains the list of your projects.
If so, you should pass a function into your Search component props, your Search component will then call this function when the user typed something in the search bar. This will help your parent element decide what your ProjectsLists needs to render :
handleInputChange = () => {
this.props.userSearchInput(this.search.value);
this.setState({
query: this.search.value
});
};
And now, here is what the parent element needs to include :
searchChanged = searchString => {
const filteredProjects = this.state.projects.filter(project => project.name.includes(searchString))
this.setState({ filteredProjects })
}
With this function, you will filter out the projects that includes the string the user typed in their names, you will then only need to put this array in your state and pass it to your ProjectsList component props
You can find the documentation of the String includes function here
You can now add this function to the props of your Search component when creating it :
<Search userSearchInput={searchChanged}/>
And pass the filtered array into your ProjectsList props :
<ProjectsList projects={this.state.filteredProjects}/>
Side note : Try to avoid using refs, the onCHnage function will send an "event" object to your function, containing everything about what the user typed :
handleInputChange = event => {
const { value } = event.target
this.props.userSearchInput(value);
this.setState({
query: value
});
};
You can now remove the ref from your code

React grid elements, show details about specified element after clicking it

i have created gird of elements (something like gallery) AllElements Component where i am mapping SingleElement Component
renderAllElements = () => (
this.state.myData.map(se => (
<SingleElement key={se.id} name={se.name} tagline={se.tagline} image_url={se.image_url}/>
)
)
)
And my SingleElement renders this, as below
render() {
return (
<div className="singleElement">
<img src={this.props.image_url} alt={this.props.name} />
<h4>{this.props.name}</h4>
<h5>{this.props.tagline}</h5>
</div>
)
}
To the point, what I want achieve? ---> After clicking on one of the elements (specyfied SingleElement) the details is shown in front of the screen (hovering over whole grid). Let's name this Component SingleElementDetails. What is the best way to achieve it? Should SingleElementDetails Component be sibling of SingleElement Component or it's child ?
You could use the AllElements state and an method to handle when/what to show.
Something like this:
class AllElements extends React.Component {
constructor(props) {
super(props);
this.state = {
myData: {},
viewingElement: null,
};
this.see = this.see.bind(this);
this.close = this.close.bind(this);
}
see(id) {
this.setState({
viewingElement: id,
});
}
close() {
this.setState({
viewingElement: null,
});
}
render() {
const { myData, viewingElement } = this.state;
return (
<div>
{myData.map(se => (
<SingleElement
key={se.id}
name={se.name}
tagline={se.tagline}
image_url={se.image_url}
see={this.see}
close={this.close}
/>
))}
{viewingElement && (
<SingleElementDetails element={myData[this.state]} />
)}
</div>
);
}
}
Then you need to fire this.props.see on the onClick event from SingleElement and use CSS to visually position SingleElementDetails over the rest of the contest.

Create and return a new component on-click in parent component

I have two components Class and Students. The Class component renders and returns a list of Classes in <li>. I want to add click events to display the Students for each Class in Class component.
I have the following in the render method of the Class component:
render(){
const renderClasses = () =>
this.props.classes.map(class => {
return (
<li>
{class.name}
//class object also has a property of 'students'
</li>
)
})
return(
<div>
{ renderClasses() }
</div>
)
}
I want to be able to click on the anchor tags and display the corresponding students for that class. Of course the Student component should receive a prop as follows:
<Students students={this.class.students} />
Thanks in advance!
You can keep a component state to save the class index that should show its students, and then add an onClick handler on the anchor to change that index.
Try the code below:
export default class Test extends Component {
constructor(props)
{
super(props);
this.state = {activeClassIndex : -1}
this.setActiveClassIndex = this.setActiveClassIndex.bind(this);
}
setActiveClassIndex(index){
this.setState({
activeClassIndex : index
})
}
render(){
const renderClasses = () =>
this.props.classes.map( ( currentClass , index ) => {
return (
<li>
<a href="#" onClick={ () => { this.setActiveClassIndex(index) } }>{currentClass.name}</a>
{this.state.activeClassIndex == index ? <Students students={currentClass.students} /> : "" }
</li>
)
})
return(
<div>
{ renderClasses() }
</div>
)
}
}

Categories

Resources