Load component on onClick of li in React - reactjs

In my application, On the launch of the application, I am displaying a character from the provided JSON file.
Upon selecting a character the UI should update and display details on each of the films the character appears in. For that, I have made MovieDetails component which makes API calls ( API URL’s provided in the JSON file and the data returned from those calls ).
I am trying to fetch movie details when user click on first li (Luke Skywalker) for that I am using a switch case in handleClick(). But it's not working. Can anyone suggest me how to update the UI when the user click on li?
App.js
import React, { Component } from 'react'
import charactersFile from "./data/characters.json"
import MovieDetails from "./MovieDetails";
import './App.css';
class App extends Component {
state = {
render: false
}
handleClick = (character) => {
console.log(character.name);
this.setState({ render: true })
}
render() {
const list = <ul>
{
charactersFile.characters.map(character => {
return <li key={character.name} onClick={() => this.handleClick(character)}>{character.name}</li>
})
}
</ul>
return (
<React.Fragment>
{this.state.render ? list : <MovieDetails />}
</React.Fragment>
)
}
}
export default App
MovieDetails.js
import React, { Component } from 'react'
import axios from 'axios';
class MovieDetails extends Component {
state = {
movies: []
}
componentDidMount() {
const PeopleUrl = `https://swapi.co/api/people/`;
const FilmUrl = `https://swapi.co/api/films/`
axios.get(`${PeopleUrl}1/`)
.then(response => Promise.all([
axios.get(`${FilmUrl}2/`),
axios.get(`${FilmUrl}6/`),
axios.get(`${FilmUrl}3/`),
axios.get(`${FilmUrl}1/`),
axios.get(`${FilmUrl}7/`)
]))
.then(result => result.map(values =>
this.setState({
movies: [
...this.state.movies,
{ title: values.data.title, release_date: values.data.release_date }
]
})))
}
render() {
console.log(this.state.title)
return (
<div className="App">
<ul>
{this.state.movies.map(movie => (
<li key={movie.title}>
{movie.title} - {movie.release_date}
</li>
))}
</ul>
</div>
)
}
}
export default MovieDetails
characters.json
{
"characters": [
{
"name": "Luke Skywalker",
"url": "https://swapi.co/api/people/1/"
},
{
"name": "C-3PO",
"url": "https://swapi.co/api/people/2/"
},
{
"name": "Leia Organa",
"url": "https://swapi.co/api/people/unknown/"
},
{
"name": "R2-D2",
"url": "https://swapi.co/api/people/3/"
}
]
}
output:

Note: React only renders DOM elements inside a render function. If you return a components inside any other functions (in your case you are returning a component inside onClick function) you should not expect react renders a new content or a component.
Right now your onClick gets called upon a user's interaction. So you need to tell react to re-render the page and based on the current state renders different contents or components. As you probably know in order for react to re-render the page you need to set the state so inside your onClick
set your state to whatever your logic is this.setState({ ... }). Then inside your render function you can check the current state value that you just set inside onClick method whether to display <MovieDetails /> or something else.
Example: https://jsfiddle.net/pr0bwg3L/5/

Related

onChange() fires twice making changes to checkboxes impossible (react app)

I did a tutorial on Scrimba (it's a website with an in-browser IDE) that had this code as one of the exercises. And it worked fine in that environment. But when I imported it into VS code and ran it using "npm start" I noticed my checkbox "onChange" events would fire twice. This caused them to not work as they essentially toggle back to the value they start at.
After this you'll see the code from the two files from the project. App.js is where the function handleChange() is defined. It is then sent as a prop to the TodoItem.js functional component. That component runs this function whenever someone clicks a checkbox. But for some reason, it runs twice in a row. I tried using onClick too and the same happens.
App.js
import React from "react"
import TodoItem from "./TodoItem"
import todosData from "./todosData"
import "./style.css"
class App extends React.Component {
constructor() {
super()
this.state = {
todos: todosData
}
this.handleChange = this.handleChange.bind(this)
}
handleChange(id) {
this.setState((prevState) => {
console.log("this gets printed 2 times")
const updatedTodos = prevState.todos.map(todo => {
if (todo.id === id) {
todo.completed = !todo.completed
}
return todo
})
return {
todos: updatedTodos
}
})
}
render() {
const todoItems = this.state.todos.map(item => <TodoItem key={item.id} item={item} handleChange={this.handleChange}/>)
return (
<div className="todo-list">
{todoItems}
</div>
)
}
}
export default App
TodoItem.js:
import React from "react"
function TodoItem(props) {
const completedStyle={
textDecoration: "line-through",
fontStyle: "italic",
color: "#cdcdcd"
}
return (
<div className="todo-item">
<input
type="checkbox"
checked={props.item.completed}
onChange={() => props.handleChange(props.item.id)}
/>
<p style={props.item.completed ? completedStyle : null}>{props.item.text}</p>
</div>
)
}
export default TodoItem
Do you have your App component wrapped in React.StrictMode by any chance? Also, you could try putting event.preventDefault() inside your onChange().

Having trouble rendering data in react component

I'm trying to render the following the 'dogName' value of the following array to the browser, but it's coming up as 'undefined':
[
{
"id": 1,
"dogName": "bruce"
},
{
"id": 2,
"dogName": "borker"
},
{
"id": 3,
"dogName": "henry"
}
]
So, first of all, the data is pulled from a database and set in state in the parent component, where's it's passed as props to the child component 'DogNameList' (which I've trimmed down to just the relevant bits):
import React from 'react';
import './styles.css'
import DogList from './DogList'
import Dogue from './Dogue'
import axios from 'axios'
import DogNameList from './DogNameList'
class App extends React.Component {
constructor(){
super()
this.state = {
**dogName:[]**
}
}
componentDidMount() {
axios.get('http://localhost:3000/dogs')
.then(res => {
this.setState({
**dogName:res.data**
})
})
.catch(error => {
console.log(error)
})
}
render() {
return (
<div>
<DogNameList **names = {this.state.dogName}**/>
<Dogue/>
</div>
);
}
}
export default App;
In DogNameList, the data is mapped over and then passed as props to the 'Dogue' component (stupid names, I know, but this is a personal project):
import React from 'react'
import Dogue from './Dogue'
const DogNameList = (props) => {
return(
<div>
{
props.names.map(name => {
console.log(name.dogName)
return <Dogue name = {name} key ={name.id}/>
})
}
</div>
)
}
export default DogNameList
finally, it's supposed to be rendered to the browser via the 'Dogue' component:
import React from 'react'
import axios from 'axios'
class Dogue extends React.Component {
constructor(props){
super(props)
this.state = {
}
}
render(){
return (
<div>
<img className = 'img' src = {this.props.dogList}/>
<br/>
<form className = 'form'>
<input type = 'text' placeholder = 'Enter dog name'/>
<br/>
<button>Submit</button>
</form>
**<h2>dog name: {this.props.name}</h2>**
</div>
)
}
}
export default Dogue
Any ideas why it's not working? I console logged the following and it returned the list of names (not as strings, I should add):
props.names.map(name => {
console.log(name.dogName)
First of all, replace this
<h2>dog name: {this.props.name}</h2>
with this
<h2>dog name: {this.props.name.dogName}</h2>
because you are creating a component with object, so name property actually holds the object, not the name property of the object.
return <Dogue name = {name} key ={name.id}/>
You also don't declare somewhere this property
{this.props.dogList}
Also to handle the undefined error messages, do this
{this.state.dogName && <DogNameList names ={this.state.dogName}/>}

ReactJS infinite loop after react-select dropdown selection

I just started teaching myself ReactJS a few weeks ago and I'm stuck trying to figure out why my API gets consistently hit in an infinite loop after selecting a value from a dropdown. I have a search component called StateSearch.js that is being rendered in the StatePolicyPage.js component.
In StatePolicyPage.js I call <StateSearch parentCallback={this.callbackFunction} /> so that I can get the value the user picked from the dropdown and set the state. In StateSearch.js I'm passing the selected value using props.parentCallback(response.data)
The problem is that an infinite loop occurs for some reason and my Rails API keeps getting called over and over instead of just returning the data one time.
(StateSearch.js) search component
import React, { useState } from 'react'
import Select from 'react-select'
import makeAnimated from 'react-select/animated';
import axios from 'axios';
import statesJSON from '../../helpers/states';
// uses 'react-select'
export default function StateSearch(props) {
const [americanState, setAmericanState] = useState();
// if a state was selected from the dropdown
if (americanState) {
axios.get("http://localhost:3001/get_stuff", {
params: {
state: americanState.value
}
}).then(response => {
// the response back from the Rails server
if (response.status === 200) {
props.parentCallback(response.data); // send data back up to parent
}
}).catch(error => {
console.log("Error fetching the state ", americanState.value, error);
})
event.preventDefault();
}
// the dropdown select box for states.
return (
<div>
<Select
options={statesJSON}
placeholder="Select a State"
onChange={setAmericanState}
noOptionsMessage={() => 'Uh-oh nothing matches your search'}
className=""
components={makeAnimated()}
isSearchable
isClearable={true}
/>
</div>
)
}
(StatePolicyPage.js) the component that the search results should be passed to
import React, { Component } from 'react'
import Navigation from './Navigation';
import Container from 'react-bootstrap/Container';
import Row from 'react-bootstrap/Row';
import Col from 'react-bootstrap/Col';
import StateSearch from './search/StateSearch';
export default class StatePolicyPage extends Component {
constructor(props) {
super(props);
this.state = {
id: '',
stateName: '',
updatedAt: '',
createdAt: ''
}
}
callbackFunction = (childData) => {
console.log(childData);
this.setState({
id: childData.id,
stateName: childData.state_name,
updatedAt: childData.updated_at,
createdAt: childData.created_at
})
}
render() {
return (
<div>
<Navigation
isLoggedIn={this.props.loggedInStatus}
user={this.props.user}
handleLogoutClick={this.props.handleLogoutClick}
handleLogout={this.props.handleLogout}
/>
<Container>
{/* get the dropdown value from the StateSearch back */}
<StateSearch parentCallback={this.callbackFunction} />
<div>
<Row>
{ this.state.id }
</Row>
</div>
</Container>
</div>
)
}
}
Always use useEffect() hook for asynchronous tasks.
useEffect(() => {
// if a state was selected from the dropdown
if (americanState) {
axios.get("http://localhost:3001/get_stuff", {
params: {
state: americanState.value
}
}).then(response => {
// the response back from the Rails server
if (response.status === 200) {
props.parentCallback(response.data); // send data back up to parent
}
}).catch(error => {
console.log("Error fetching the state ", americanState.value, error);
})
}
}, [americanState]);
This line looks to me like it could cause an infinite loop:
components={makeAnimated()}
I'm not entirely sure what this function is doing, but when passing functions to another component you can't directly invoke them.
Try replacing the above line with this:
components={makeAnimated}
or with this:
components={() => makeAnimated()}

Using ReactJs to fetch data from an API but getting completely blank page with no errors

Guys Kindly i need your help. I am trying to fetch data from an Api and display it in the dom. I can see the data in the console but when i try to return data it shows a blank page and no errors. Below is my code.
App.js file
import React from "react";
import "./App.css";
import Movieapp from "./Movieapp";
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
title: [],
date: [],
image: []
};
}
componentDidMount() {
fetch(`https://yts.mx/api/v2/list_movies.json?quality=3D`)
.then(res => res.json())
.then(data => {
console.log(data.data);
this.setState = {
title: data.data.movies[0].title,
date: data.data.movies[0].date_uploaded,
image: data.data.movies[0].background_image
};
});
}
render() {
return (
<div className="App">
<Movieapp
title={this.state.title}
date={this.state.date}
image={this.state.image}
/>
</div>
);
}
}
export default App;
Movieapp.js file
import React from "react";
const Movieapp = props => {
return (
<div>
<h1>{props.title}</h1>
<h1>{props.date}</h1>
<div>{props.image}</div>
</div>
);
};
export default Movieapp;
this.setState is a function, not a property. You have to use it properly:
this.setState({
title: data.data.movies[0].title,
date: data.data.movies[0].date_uploaded,
image: data.data.movies[0].background_image
});
Also, even though I guess you are just trying things our, there are few things to be aware of:
movies[0] can be undefined
You are getting multiple movies but showing only one. It's probably better to just save the whole data array in the state and iterate over the results in the render method

Passing data into another component

I'm trying to pass data into another component, but I'm having trouble re-rendering state so it's not a blank array when it passes the component.
Movie Card Component:
import React, { Component } from 'react';
import getMovies from './MovieAPI.js';
import MoviePoster from './MoviePoster.js';
class MovieCardII extends Component {
constructor() {
super();
this.state = {
movies: [],
};
}
componentDidMount() {
getMovies().then(results => {
this.setState(results.Search: movies)
console.log("state", this.state);
console.log(results.Search);
});
}
render() {
const { movies } = this.state;
return (
<div>
<h1> Hi </h1>
<MoviePoster movies={movies} />
</div>
);
}
}
export default MovieCardII;
MoviePoster Component
import React from 'react';
import PropTypes from 'prop-types';
const MoviePoster = props => {
const { movies } = props;
console.log(movies);
return (
<div>
{movies.map(movie => (
<div>
<h1> {movie.Poster} </h1>
</div>
))}
</div>
);
};
MoviePoster.propTypes = {
movies: PropTypes.array.isRequired,
};
export default MoviePoster;
I'm using the OMDB API and the MovieCard component is able to get a state with an array list of 10 once I am able to get the get request in.
But in the MoviePoster component, the movie array remains an empty array.
New to React so I'm struggling to understand how to pass data into another component. I am able to get the create the view I want if I don't have to pass data to another array, but I need to create one since the API i'm using is not able to get all the information I need to make another API request using the movie ID in another component later. So basically the set up will be
movieCard is the parent
movie poster, and movieInfo will be the children.
The movieInfo will pull another API request using the imdbID that I get from the movieCard component.
The way you have set the state is wrong
Movie Card Component:
componentDidMount(){
getMovies().then(results=> {
this.setState(results.Search: movies)
console.log("state", this.state);
console.log(results.Search);
});
}
Solution: Movie Card Component
componentDidMount(){
getMovies().then(results=> {
this.setState({movies: results.Search})
console.log("state", this.state);
console.log(results.Search);
});
}
One more change is required to MoviePoster Component
You need to specify key whenever you are looping
const MoviePoster = (props) => {
const { movies } = props;
console.log(movies);
return (
<div>
{movies.map(movie => (
<div key={movies.id}>
<h1> {movie.Poster}</h1>
</div>
))}
</div>
)
}
Hope this solution will help you.

Resources