How can I update the this.state.songs to songsList - reactjs

I cant update the state songs which needs to get values from songsList . How can I update the songs to songsList ? Is it anything to do with the component life cycle ? While running the below code , 'songsList is undefined' error throws up . const songList is in the render .
import React, { Component } from 'react';
import logo from './components/Logo/box8.png';
import './App.css';
import SearchBox from './components/SearchBox/SearchBox';
import SongCards from './components/SongCards/SongCards';
import 'tachyons';
import axios from 'axios';
class App extends Component {
state = {
songs : [],
searchField: '',
entries: []
};
componentDidMount() {
axios.get(`https://itunes.apple.com/in/rss/topalbums/limit=100/json`)
.then(response =>
{this.setState({ entries: response.data.feed.entry });
});
}
onSearchChange=(event)=>{
this.setState({songs : songsList})
this.setState({searchField : event.target.value})
const filteredSongs = this.state.songs.filter(song =>{
return song.title.toLowerCase().includes(this.state.searchField.toLowerCase())
});
}
render(){
const songsList = this.state.entries.map(entries => {
return (
<SongCards
key={entries.id.label}
artist={entries["im:artist"].label}
image={entries["im:image"][2].label}
link={entries.id.label}
price={entries["im:price"].label}
date={entries["im:releaseDate"].label}
title={entries.title.label}
/>
);
});
return (
<div className="App">
<header className="App-header">
<img src={logo} className="App-logo" alt="logo" />
</header>
<SearchBox searchChange= {this.onSearchChange}/>
{songsList}
</div>
);
}
}
export default App;

Appreciate all your responses . I made it finally .
import React, { Component } from 'react';
import logo from './components/Logo/box8.png';
import './App.css';
import SearchBox from './components/SearchBox/SearchBox';
import Albums from './components/Albums/Albums';
import Scroll from './components/Scroll/Scroll';
import 'tachyons';
import emoji from 'emoji-dictionary';
import axios from 'axios';
class App extends Component {
state = {
show:false,
songs : [],
searchField: '',
};
componentDidMount() {
axios.get(`https://itunes.apple.com/in/rss/topalbums/limit=100/json`)
.then(response =>
{this.setState({songs:response.data.feed.entry });
});
}
itunesPageLoader=()=>{
this.setState({show:false})
}
onSearchChange=(event)=>{
this.setState({searchField : event.target.value})
}
render(){
const filteredSongs = this.state.songs.filter(song =>{
return
song.title.label.toLowerCase().includes(this.state.searchField.toLowerCase())
})
return (
<div className="App">
<header className="App-header">
<img src={logo} className="App-logo" alt="logo" />
</header>
<SearchBox searchChange= {this.onSearchChange}/>
<Scroll >
<Albums songs={filteredSongs}/>
</Scroll>
<footer className="pv4 ph3 ph5-m ph6-l red">
<small className="f6 db tc">© 2018 <b className="ttu">Box8 Inc</b>., All
Rights Reserved</small>
<div className="tc mt3">
{`Made with ${emoji.getUnicode("purple_heart")} by Renjith`}
</div>
</footer>
</div>
);
}
}
export default App;

Try this. You are actually assigning songsList to songs using setState but the songsList doesn’t exist in onSearchChange. To push searched value to an array you need to push event.target.value to songs array
Try with below corrected code
onSearchChange=(event)=>{
this.setState(prevState => ({songs : [...prevState.songs, event.target.value]}));
this.setState({searchField : event.target.value})
const filteredSongs = this.state.songs.filter(song =>{
return song.title.toLowerCase().includes(this.state.searchField.toLowerCase())
});
}

You have mentioned that this.state.entries is an Object.
If this is true, then yo can't perform .map on it as .map is an Array method.
You can however use Object.entries to get an array of [key,value] pairs of this.state.entries.
Object.entries(this.state.entries).map(([key,value]) => ...)
Simple running example:
const object1 = { foo: 'this is foo', baz: "this is baz" };
Object.entries(object1).map(([key,value]) => console.log(`key: ${key}, value: ${value}`));

So i will do something like this:
const IN_PROGRESS = 'IN_PROGRESS';
const SUCCESS = 'SUCCESS';
class App extends Component {
state = {
songs : null,
entries: null,
status: null
};
componentDidMount() {
this.setState({status: IN_PROGRESS});
axios.get(`https://itunes.apple.com/in/rss/topalbums/limit=100/json`)
.then({data} => {
const songs = data.feed.entry;
this.setState({entries: songs});
this.setState({songs});
this.setState({status: SUCCESS});
});
}
onSearchChange = ({target}) => {
const {value} = target;
const songs = this.state.entires.filter(song =>
song.title.toLowerCase().includes(value.toLowerCase())
});
this.setState({songs});
}
render() {
const {status, songs} = this.state;
return (
<div className="App">
<header className="App-header">
<img src={logo} className="App-logo" alt="logo" />
</header>
<SearchBox searchChange={this.onSearchChange}/>
{
status === IN_PROGRESS &&
(/* you can insert here some kind of loader which indicates that data is loading*/)
}
{
status === SUCCESS && songs.map(entry => {
const {
id, ['im:artist']: artist, ['im:image']: image,
['im:price']: price, ['im:releaseDate']: date, title
} = entry;
return (
<SongCard
key={id.label}
artist={artist.label}
image={image[2].label}
link={id.label}
price={price.label}
date={date.label}
title={entry.title.label}
/>
)
}
}
{
//Here you can display error message if status === FAILURE
}
</div>
);
}
}
When component did mount, I set status into IN_PROGRESS (if you want some kind of loader to show), and data are beeing fetched - axios.get is asynchronous so remember that when data is fetching then render method is already triggered. When data is loaded then in state I hold two variables, entries which holds unfiltered list of songs, and songs which holds filteres songs.
When search is triggered then I filter entires by searched phrase and set into state this filtered array.
Component renders songCards mapping by filtered songs

Related

How to display a list of dynamically loaded components in nextJS

So in my homepage I am building a list of decks per category.
Something like:
Category A:
Deck 1 | Deck 2 | Deck 3
Category B:
Deck 4 | Deck 6
Category C:
Deck 7
I know how I can load all categories from the server in my homepage, but I don't know how to load the deck lines per category.
I imagine I would be best served with a component that receives as a parameter the categoryName, and outputs the list of decks.
How I list my categories:
// Import the dependency.
import clientPromise from '../mongodb-client';
async function fetchCategoriesFromDB(context, session) {
const client = await clientPromise;
const collection = await client.db().collection('categories');
let mySort= {name: 1};
const categories= await collection.find().sort(mySort).toArray();
const categoryList = JSON.parse(JSON.stringify(categories));
return categoryList;
}
export async function getServerSideProps(context) {
const categoryList = session ? await fetchCategoriesFromDB(session): '';
return {
props: {
categoryList,
}
}
}
export default function Home({categoryList}) {
const [categories, setCategories] = useState(categoryList);
// When rendering client side don't display anything until loading is complete
if (typeof window !== 'undefined' && loading) return null
// If no session exists, display access denied message
if (!session) { return <Layout><AccessDenied/></Layout> }
// If session exists, display content
const isAdmin = session.user.email === process.env.NEXT_PUBLIC_EMAIL_ADMIN;
return (
<Layout>
<Head>
<title>CardX</title>
<meta name="description" content="A Card Repository" />
<link rel="icon" href="/favicon.ico" />
</Head>
<div className={styles.grid}>
{categories.map(({_id, name, description, url }) => (
<div className={styles.categoryItem} key={_id} >
{url && <img src={url} class={styles.category} /> }
{name}
<br />
{description}
<CategoryDecks categoryName={name} />
</div>
))}
</div>
...
And how I imagined my component to be:
import styles from '../styles/Home.module.css'
async function fetchCategoryDecksFromDB(categoryName) {
const client = await clientPromise;
const collection = await client.db().collection('decks');
let mySort= {name: 1};
const decks= await coldecks.find({categories:categoryName}).sort(mySort).toArray();
const deckList = JSON.parse(JSON.stringify(decks));
return deckList;
}
export async function getServerSideProps(context) {
const deckList = await fetchCategoryDecksFromDB(context.categoryName);
return {
props: {
deckList,
}
}
}
export default function CategoryDecks({deckList}){
return(<>
<div className={styles.categoryLine}>
{deckList.map(({ _id, name, description, url }) => (
<div className={styles.card} key={_id} >
<a href={"/decks/"+_id} >
{url && <img src={url} class={styles.deck} /> }
{name}<br />
{description}
</a>
</div>
))}
</div> </>)
}
The thing is:
I don't think I can call getServerSideProps from inside the component and don't know how to do it any other way. (What ways can there be to fetch the data for the component?)
I don't know how to pass arguments to the component and use them there (Do I need dynamic import for this?)
For now, following https://nextjs.org/docs/basic-features/data-fetching/client-side, instead of using a component I opted to put everything in the same page:
import Head from 'next/head'
import styles from '../styles/Home.module.css'
import { useState } from 'react'
import Layout from '../components/layout'
import { useSession, getSession } from 'next-auth/client'
import AccessDenied from '../components/access-denied'
import React from "react";
import useSWR from 'swr'
"use strict";
// Import the dependency.
import clientPromise from '../mongodb-client';
/*import dynamic from 'next/dynamic'
const CategoryDecks = dynamic(() => import('../components/categoryDecks'))
*/
async function fetchCategoriesFromDB(context, session) {
const client = await clientPromise;
const collection = await client.db().collection('categories');
let mySort= {name: 1};
const categories= await collection.find().sort(mySort).toArray();
const categoryList = JSON.parse(JSON.stringify(categories));
return categoryList;
}
export async function getServerSideProps(context) {
const session = await getSession(context);
const categoryList = session ? await fetchCategoriesFromDB(session): '';
return {
props: {
categoryList,
}
}
}
const fetcher = (...args) => fetch(...args).then((res) => res.json())
function CategoryDecks({categoryName}) {
const { data, error } = useSWR('/api/categories/'+categoryName+'/decks', fetcher)
if (error) return <div>Failed to load</div>
if (!data) return <div>Loading...</div>
return (
<div className={styles.categoryLine}>
{data.map(({ _id, name, description, url }) => (
<div className={styles.card} key={_id} >
<a href={"/decks/"+_id} >
{url && <img src={url} class={styles.deck} /> }
{name}
</a>
</div>
))}
</div>
)
}
export default function Home({cardList, deckList,categoryList}) {
const [ session, loading ] = useSession();
const [categories, setCategories] = useState(categoryList);
// When rendering client side don't display anything until loading is complete
if (typeof window !== 'undefined' && loading) return null
// If no session exists, display access denied message
if (!session) { return <Layout><AccessDenied/></Layout> }
// If session exists, display content
const isAdmin = session.user.email === process.env.NEXT_PUBLIC_EMAIL_ADMIN;
return (
<Layout>
<Head>
<title>CardX</title>
<meta name="description" content="A Card Repository" />
<link rel="icon" href="/favicon.ico" />
</Head>
<div>
{categories.map(({_id, name, description, url }) => (
<>
<div className={styles.categoryLine} key={_id} >
{url && <img src={url} class={styles.category} /> }
{name}
</div>
<CategoryDecks categoryName={name} />
</>
))}
</div>
</Layout>
)
}

prevent render attempt if item not loaded from state yet

I am requesting an image from cloudinary and at face value everything seems fine as I can see the image on the frontend as intended. But when looking at chrome dev tools I can see that first there was a 404 error which shows a call to the path where the image is stored but without the image name. For the second call which is successful, there is the path and the image name.
So, It appears that before the image name is not yet loaded from the state at the time the first request is made. I did try the && conditional check but that had the same result ie:
{this.state.bgImg && this.state.bgImg}
Then I tried:
{this.state.bgImg ? this.state.bgImg : "fakeImage.jpg"}
And in dev tools I see it actually tried to get that fakeImage.jpg
How can I prevent this?
class Home extends Component {
state = {
title: "",
bgImg: "",
categories: []
};
async componentDidMount() {
const response = await getHero();
const { data: categories } = await getCategories();
this.setState({
title: response.data.title,
categories,
bgImg: response.data.bgImg
});
}
render() {
return (
<React.Fragment>
<NavBar />
<Hero
title={this.state.title}
bgImg={this.state.bgImg && this.state.bgImg}
/>
</React.Fragment>
);
}
}
export default Home;
const imageUrl = process.env.REACT_APP_CLOUDINARY_URL;
class Hero extends Component {
render() {
const { title, bgImg } = this.props;
return (
<section
className="section-hero d-flex justify-content-center align-items-center mb-5"
style={{
backgroundImage: `linear-gradient(rgba(0, 0, 0, 0.2), rgba(0, 0, 0, 0.2)), url(${imageUrl}/somepath/${bgImg})`
}}
>
<Container className="text-center text-white">
<h1>{title}</h1>
</Container>
</section>
);
}
}
export default Hero;
For the initial render of your Home component, you pass an empty string to Hero for the bgImg prop. You will get a 404 error because no image was found in this path.
url(${imageUrl}/somepath/${bgImg}) <--- bgImg is an empty string on first render.
To workaround this, you can just do a conditional check so that your Hero component only renders when the bgImg-state in Home is a truthy value, which it will be after the completed fetch in componentDidMount.
So for the first render we will give bgImg a default value of null (that makes sense) because there is no-value. Hero component will not be used yet (so no 404 error). Then after componentDidMount, everything will work as expected.
class Home extends Component {
state = {
title: "",
bgImg: null,
categories: []
};
async componentDidMount() {
const response = await getHero();
const { data: categories } = await getCategories();
this.setState({
title: response.data.title,
categories,
bgImg: response.data.bgImg
});
}
render() {
const { bgImg } = this.state
return (
<React.Fragment>
<NavBar />
{ bgImg && (
<Hero
title={this.state.title}
bgImg={bgImg}
/>
)}
</React.Fragment>
);
}
}
export default Home;

How to update state before rendering in Reactjs with mobx

This is my mobx store code.
First, 'projectGet()' must be executed to push the data from firestore.
#observable projectState = {
projects: []
};
projectGet = () => {
firebase
.firestore()
.collection("projects")
.get()
.then(snapshot => {
snapshot.forEach(doc => {
this.projectState.projects.push(doc.data());
});
})
.catch(err => {
console.log("Error getting documents", err);
});
};
After push the data into projectState, it should be read at the other .js file.
I ran the function inside of render.
But when I enter the homepage, it doesn't update state at first.
So, when I refresh the homepage, it updates the state.
However, I need to update the state at the first home page access.
I tried to use 'componentWilupdate', 'ComponentDidmount' etc.
It doesn't work at all.
Could you give me some recommendation for this problem?
render() {
const { Project } = this.props;
Project.projectGet();
return (
<div className="col s12 m6">
<ProjectList projects={Project.projectState.projects} />
</div>
);
}
I attached more code below.
import React from "react";
import ProjectSummary from "./ProjectSummary";
import { Link } from "react-router-dom";
const ProjectList = ({ projects }) => {
return (
<div className="project-list section">
{projects &&
projects.map(project => {
return (
<Link to={"/project/" + project.id} key={project.id}>
<ProjectSummary project={project} />
</Link>
);
})}
</div>
);
};
export default ProjectList;
You can use componentDidMount lifecycle method to make API calls before rendering, For example
componentDidMount() {
const { Project } = this.props;
Project.projectGet();
}
then in render
render() {
const { Project } = this.props;
return (
<div className="col s12 m6">
<ProjectList projects={Project.projectState.projects} />
</div>
);
}
Use componendWillMount to make API call before the component renders, if it is not updated then check whether the expected props are available with componentWillReceiveProps lifecycle method.
componentWillReceiveProps({Project}) {
Project.projectGet();
}
Once the props are changed you will get the change in the render

My search input and pagination aren't triggering anything in Reactjs

I'm fairly new to react.
My search input and pagination buttons aren't triggering anything and nothing comes up in the console, what is wrong with my code ?
I tried putting every functions in App.js to get it cleaner.
App.js
import React, { Component } from "react";
import List from './List';
let API = 'https://swapi.co/api/people/';
class App extends Component {
constructor(props) {
super(props);
this.state = {
results: [],
search: '',
currentPage: 1,
todosPerPage: 3
};
this.handleClick = this.handleClick.bind(this);
this.updateSearch = this.updateSearch.bind(this);
}
componentWillMount() {
this.fetchData();
}
fetchData = async () => {
const response = await fetch(API);
const json = await response.json();
this.setState({ results: json.results });
};
handleClick(event) {
this.setState({
currentPage: Number(event.target.id)
});
}
updateSearch(event) {
this.setState({
search: event.target.value.substr(0, 20)
});
}
render() {
return (
<div>
<List data={this.state} />
</div>
);
}
}
export default App;
List.js
import React, { Component } from 'react';
import Person from './Person';
class List extends Component {
render() {
const { data } = this.props;
const { results, search, updateSearch, handleClick, currentPage, todosPerPage } = data;
const indexOfLastTodo = currentPage * todosPerPage;
const indexOfFirstTodo = indexOfLastTodo - todosPerPage;
const currentTodos = results.slice(indexOfFirstTodo, indexOfLastTodo).filter(item => {
return item.name.toLowerCase().indexOf(search) !== -1;
});
const renderTodos = currentTodos.map((item, number) => {
return (
<Person item={item} key={number} />
);
});
const pageNumbers = [];
for (let i = 1; i <= Math.ceil(results.length / todosPerPage); i++) {
pageNumbers.push(i);
}
const renderPageNumbers = pageNumbers.map(number => {
return (
<li className="page-link" key={number} id={number} onClick={handleClick} style={{cursor: "pointer"}}>{number}</li>
);
});
return (
<div className="flex-grow-1">
<h1>Personnages de Star Wars</h1>
<form className="mb-4">
<div className="form-group">
<label>Rechercher</label>
<input
className="form-control"
type="text"
placeholder="luke skywalker..."
value={search}
onChange={updateSearch}
/>
</div>
</form>
<div className="row mb-5">{renderTodos}</div>
<nav aria-label="Navigation">
<ul id="page-number" className="pagination justify-content-center">{renderPageNumbers}</ul>
</nav>
</div>
);
}
}
export default List;
The value of the input doesn't change one bit if I type in it and if I right click on a page number, the console gets me Uncaught DOMException: Failed to execute 'querySelectorAll' on 'Element': '#4' is not a valid selector.
Any idea ?
The issue is that in the List class you attempt take updateSearch and handleClick out of data (which in turn comes from this.props). But updateSearch and handleClick are never placed inside data. If you log either of these methods to the console you'll see they are undefined.
To fix this, you need to pass updateSearch and handleClick from App to List. You can do this either by including the methods inside the data prop, or by passing them directly as their own props (which I would recommend).
For example, you can change the render method of App to look something like this:
render() {
return (
<div>
<List
data={this.state}
updateSearch={ this.updateSearch }
handleClick={ this.handleClick }
/>
</div>
);
}
Then in the render method of List you can do this:
const { data, updateSearch, handleClick } = this.props;
and remove the definitions of the two methods from the destructuring of data below.

Rendering specific component using switch case --- React

My requirement is to render components based on user selection.
I have a left nav on click of which I am trying to render the component associated with it but I am getting error:
Error:
Functions are not valid as a React child. This may happen if you return a Component instead of from render. Or maybe you meant to call this function rather than return it.
My code goes as under:
------ManageData.js-------
import React, {
Component
} from 'react';
import NestedList from '../Left-nav/leftnav';
import '../Left-nav/leftnav.css';
import AddCarousel from '../addCarousel/addCarousel';
import AddClass from '../addClass/addClass';
import './manageData.css';
class ManageData extends Component {
loadComponent = null;
constructor(props) {
super(props);
this.state = {
showChild: 1
};
//this.generateComponent = this.generateComponent.bind(this);
}
loadComponents = (data) => {
console.log(data, this.state);
if (data == 'AddClass') {
this.setState({
showChild: 2
}, () => {
console.log('state changed-----', this.state)
//this.generateComponent(data, this.state);
})
}
}
render() {
const showItem = this.state.showChild;
return (
<section className = "Admin-add-video">
<div className="row">
<div className = "col-3 set-padding" > < NestedList loadComponents = {this.loadComponents}/>
</div >
<div className = "col-9 set-ht" >
{ this.state.showChild == 1 && <AddCarousel/> }
{this.state.showChild == 2 && <AddClass/>}
</div>
</div>
</section>
);
}
}
export default ManageData;
Nested List is the separate component on click of its item I am getting the value and trying to setState().
I have tried everything from this url : using switch statement for react rendering
But for all the cases I am getting same error.
May be I am missing anything. Any help will be highly appreciated.
It looks like the problem is with AddClass component. Pl double check if it is exported correctly.
Note: Posting this answer from my comment on the question as it fixed OP's error.
Try this in the render method:
render() {
const { showChild } = this.state;
const renderChilds = () => {
switch(showChild) {
case 1:
return <AddCarousel />;
case 2:
return <AddClass />;
default:
return <div />;
}
};
return (
<section className="Admin-add-video">
<div className="row">
<div className = "col-3 set-padding">
< NestedList loadComponents={this.loadComponents} />
</div >
<div className = "col-9 set-ht" >
{renderChilds()}
</div>
</div>
</section>
);
}

Resources