Refactoring Search component from App js to Search js - javascript

So im following along with a book "The Road To React" By: Robin Wieruch. The book has you do everything inside the App.js file...which is extremely disappointing because this is obviously terrible practice. Anyway, ive extracted everything else to their own component files and i cant figure out how to extract the search to its own file. Ive attached an img showing the folder structure.
In App.js im trying to extract everything, leaving a return that returns the components like it should be. As it is the App.js file looks like this:
import React, { useState } from 'react';
import './App.css';
import Greeting from '../Greeting/Greeting';
import Search from '../Search/Search';
import List from '../List/List';
const useSemiPersistentState = (key, initialState) => {
const [value, setValue] = React.useState(
localStorage.getItem(key) || initialState
);
React.useEffect(() => {
localStorage.setItem(key, value);
}, [value, key]);
return [value, setValue]
};
// App component
const App = () => {
const stories = [
{
title: 'React',
url: 'https://reactjs.org/',
author: 'Jordan Walke',
num_comments: 3,
points: 4,
objectID: 0,
},
{
title: 'Redux',
url: 'https://redux.js.org/',
author: 'Dan Abramov, Andrew Clark',
num_comments: 2,
points: 5,
objectID: 1,
},
];
// Set state on searchTerm, setSearchTerm with custom hook
const [searchTerm, setSearchTerm] = useSemiPersistentState(
'search',
'React'
);
// Get the value of search input
const handleSearch = (event) => {
setSearchTerm(event.target.value);
};
// Check if user input matches stories array
// toLowerCase() both values
const searchedStories = stories.filter((story) => story.title.toLowerCase().includes(searchTerm.toLowerCase())
);
// Render
return (
<div className="App">
<Greeting name="Colin" age="28" occupation="Front-end developer" />
<InputWithLabel
id="search"
value={searchTerm}
isFocused
onInputChange={handleSearch}
>
<strong>Search:</strong>
</InputWithLabel>
<hr />
<List list={searchedStories} />
</div>
);
}
// Search bar
// Destructure props search, onSearch
const InputWithLabel = ({ id, value, type = "text", onInputChange, isFocused, children, }) => {
const inputRef = React.useRef();
React.useEffect(() => {
if (isFocused && inputRef.current) {
inputRef.current.focus();
}
}, [isFocused]);
return (
<>
<label htmlFor={id}>{children}</label>
<input
ref={inputRef}
id={id}
type={type}
value={value}
autoFocus={isFocused}
onChange={onInputChange}
/>
</>
)
}
export default App;
The List component which is a parent of the Item component looks like so:
import React from 'react';
import Item from '../Item/Item';
function List({ list }) {
return (
<ul>
{list.map(({ objectID, ...item }) => (
<Item key={objectID} {...item} />
))}
</ul>
);
}
export default List;
The Item component looks like so:
import React from 'react';
function Item({ title, url, author, num_comments, points }) {
return (
<div>
<li>
<span>
<a href={url}>{title}</a>
</span>
<span> {author}</span>
<span> {num_comments} comments,</span>
<span> {points} points.</span>
</li>
</div>
);
}
export default Item;
The Greeting Component and Helpers folder are unrelated in any way so i wont post them.
Just as a note all of the code in here works... and at the time im not really interested in refactoring this unless you care to. Im just trying to extract all of that nonsense that relates to what should be a separate Search component out of App.js and into Search.js. Have been trying and ive hit a wall with this as im still new with react.
Here are the errors shown once i move all of the Search content related code from App.js to Search.js and attempt to import to the Search component into App.js
****Failed to compile****
src/Components/App/App.js
Line 60:8: 'InputWithLabel' is not defined react/jsx-no-undef
Line 62:16: 'searchTerm' is not defined no-undef
Line 64:24: 'handleSearch' is not defined no-undef
Line 69:19: 'searchedStories' is not defined no-undef
Search for the keywords to learn more about each error.

Related

React child callback not being executed after being passed down twice

I am working on the following project https://github.com/codyc4321/react-udemy-course section 11 the videos app. The udemy course is found at https://www.udemy.com/course/react-redux/learn/lecture/12531374#overview.
The instructor is passing a callback down to multiple children and calling it in the lowest videoItem and the code is supposed to console log something out. I have no console log in my browser even though I've copied the code as written and double checked for spelling errors.
At the main level is App.js:
import React from 'react';
import youtube from '../apis/youtube';
import SearchBar from './SearchBar';
import VideoList from './VideoList';
class App extends React.Component {
state = {videos: [], selectedVideo: null};
onTermSubmit = async term => {
const response = await youtube.get('/search', {
params: {
q: term
}
});
// console.log(response.data.items);
this.setState({videos: response.data.items});
};
onVideoSelect = video => {
console.log('from the app', video);
}
render() {
return (
<div className="ui container">
<SearchBar onFormSubmit={this.onTermSubmit} />
<VideoList
onVideoSelect={this.onVideoSelect}
videos={this.state.videos} />
</div>
)
}
}
export default App;
videoList.js
import React from 'react';
import VideoItem from './VideoItem';
const VideoList = ({videos, onVideoSelect}) => {
const rendered_list = videos.map(video => {
return <VideoItem onVideoSelect={onVideoSelect} video={video} />
});
return <div className="ui relaxed divided list">{rendered_list}</div>;
};
export default VideoList;
the videoItem.js
import React from 'react';
import './VideoItem.css';
const VideoItem = ({video, onVideoSelect}) => {
return (
<div onClick={() => onVideoSelect(video)} className="item video-item">
<img
src={video.snippet.thumbnails.medium.url}
className="ui image"
/>
<div className="content">
<div className="header">{video.snippet.title}</div>
</div>
</div>
);
}
export default VideoItem;
The code that isn't running is
onVideoSelect = video => {
console.log('from the app', video);
}
My guess is that it has something to do with a key prop not being present in the map - I'm not super well versed with class components but I can't find anything else funky so maybe try adding a unique key prop in the map.
When rendering components through a map react needs help with assigning unique identifiers to keep track of re-renders etc for performance, that also applies to knowing which specific instance called a class method.
If you don't have a unique ID in the video prop you can use an index in a pinch, although ill advised, it can be found as the second parameter in the map function. The reason it's ill advised to use an index is if there are multiple children with the same index in the same rendering context, obviously the key parameter could be confused.
Okay-ish:
const rendered_list = videos.map((video, index) => {
return <VideoItem key={index} onVideoSelect={onVideoSelect} video={video} />});
Better:
const rendered_list = videos.map((video, index) => {
return <VideoItem key={video.id} onVideoSelect={onVideoSelect} video={video} />});

map function not rendering jsx? Error: Objects are not valid as a React child

I'm working on the comments feature of my assignment. I'm trying to display the author name of the comments from my object. However, my map function doesnt seem to be working as whenever I click the button I get an error saying Error: Objects are not valid as a React child (found: object with keys {roles, _id, username, email, password, __v}). If you meant to render a collection of children, use an array instead.
Reviews,js
import React, {useState, useRef} from 'react';
import Axios from 'axios';
import {Button, Input} from 'antd';
import authService from '../../services/auth.service'
import authHeader from '../../services/auth-header';
import FirstReview from './FirstReview';
const {TextArea} = Input;
const Reviews = (props) => {
const currentUser = authService.getCurrentUser();
const [review, setReview] = useState('');
const handleChange = (e) => {
setReview(e.target.value)
}
const onSubmit = (e) => {
e.preventDefault();
const variables = {
movieId: props.movieId,
content: review,
author: currentUser.id,
reviewId: props.reviewId,
}
Axios.post('http://localhost:8080/api/review/addReview', variables,{ headers: authHeader()})
.then(response=> {
if(response.data.success) {
setReview("")
props.refreshFunction(response.data.result)
} else {
alert('Failed to save review')
}
})
}
return (
<div>
<p>Reviews</p>
{props.reviewList && props.reviewList.map((review, index) => (
(!review.responseTo &&
<React.Fragment key={review._id}>
<FirstReview review={review} movieId={props.movieId} refreshFunction={props.refreshFunctions}/>
</React.Fragment>
)))}
<form style={{display: 'flex'}} onSubmit>
<TextArea
style={{width: '100%', borderRadius: '5px'}}
placeholder = "leave a review"
value={review}
onChange={handleChange}
/>
<Button style = {{width: '20%', height: '52px'}} onClick={onSubmit}></Button>
</form>
</div>
);
};
export default Reviews
FirstReview.js
import React from 'react'
import { Button, Comment, Form, Header, TextArea } from 'semantic-ui-react'
const action = [
<span onClick key="comment-basic-reply-to">reply</span>
]
function FirstReview(props) {
// const authorName = props.review.author;
// const author = {
// authorName: authorName
// }
return (
<div>
<Comment>
<Comment.Content>
<Comment.Author as='a'> {props.review.author} </Comment.Author>
</Comment.Content>
</Comment>
<form style={{display: 'flex'}} onSubmit>
<TextArea
style={{width: '100%', borderRadius: '5px'}}
placeholder = "leave a review"
value={Comment}
onChange
/>
</form>
</div>
)
}
export default FirstReview
I have a file called movie-info.component which has a container which uses the Review component.
<Container2>
<Reviews refreshFunction={updateReview} reviewList={reviewList} movieId={movieInfo?.id}/>
</Container2>
This is an assignment question so I can't give you the full answer, but what your error message means is that you're taking an object somewhat this:
let thing = { first_name: "Tom", last_name: "Jack" }
and then doing this:
<p>{thing}</p>
and although you might be expecting to see "Tom Jack" in the above, you will get the error that you've suggested because React is unable to turn the object into anything that would meaningfully fit inside a p tag.
Do this instead:
<p>{thing.first_name + thing.last_name}</p>
which is specific enough for React to validly render.

Render component stored in record in React with dynamic string key

This is something that is easy to work around, but I was wondering if this is possible. Is it achievable at all to render a React component by accessing an object with a dynamic key?
Trying the below shows that the expected way of doing it is invalid syntax in JSX. I understand I could store the active object in the map in the state or conditionally map the object entries, but I couldn't seem to find any questions regarding this and was hoping to see if anyone has any experience with this.
Thanks for your help.
Here's the setup I have:
import React, { useState } from "react"
import {ComponentOne, ComponentTwo, ComponentThree} from "../directory"
const map = {
k1 = { name="Component 1", child=ComponentOne }
k2 = { name="Component 2", child=ComponentTwo }
k3 = { name="Component 3", child=ComponentThree }
}
const myComponent = () => {
const [active, setActive] = useState("k1")
return (
<>
<div>
{
Object.entries(map).map(([k, v]) =>
<h1 onClick={() => setActive(k)}>{ v.name }</h1>
)
}
</div>
<div>
< map[active].child />
</div>
<>
)
}
All components in the end are functions or classes that you can get a reference to and therefore access dynamically through any object.
JSX is simply a unique syntax to call that function. So first get a reference to the React component and then use JSX to render the component.
Solution Code:
import React, { useState } from "react";
import { ComponentOne, ComponentTwo, ComponentThree } from "../directory";
const map = {
k1: { name: "Component 1", child: ComponentOne },
k2: { name: "Component 2", child: ComponentTwo },
k3: { name: "Component 3", child: ComponentThree },
};
const myComponent = () => {
const [active, setActive] = useState("k1");
// Store the reference to the component you want to render in a variable
const ActiveChild = map[active].child;
return (
<React.Fragment>
<div>
{Object.entries(map).map(([k, v]) => (
<h1 onClick={() => setActive(k)}>{v.name}</h1>
))}
</div>
<div>
{/* Since the variable holds reference to a React component, you can render it JSX syntax */}
<ActiveChild />
</div>
</React.Fragment>
);
};

React and PropTypes

Some time ago I tried to make my first steps in React. Now I'm trying to create simple ToDo list app. One of my colleagues told me that it will be nice to use PropTypes with shape and arrayOf. I have one component which pass data to another and here is my issue(List.js and ListItem.js). I'm not sure when, where and how to use propTypes with shape and arrayOf. Which approach is right? Define PropTypes in parent component or in child component? Maybe there is a better idea? Please find my code below, it will be nice if you can clarify me how to use PropTypes. I tried to search something in Web, but I still don't get it.
Main App.js file:
import React from "react";
import "./styles/App.scss";
import List from "./components/List/List";
import AppHeader from "./components/AppHeader/AppHeader";
function App() {
return (
<div className="App">
<AppHeader content="TODO List"></AppHeader>
<List></List>
</div>
);
}
export default App;
AppHeader.js file:
import React from "react";
import "./AppHeader.scss";
export default function AppHeader(props) {
return (
<div className="header">
<h1 className="header-headline">{props.content}</h1>
</div>
);
}
List.js file:
import React from "react";
import "./List.scss";
import ListItem from "../ListItem/ListItem";
import _ from "lodash";
const initialList = [
{ id: "a", name: "Water plants", status: "done" },
{ id: "b", name: "Buy something to eat", status: "in-progress" },
{ id: "c", name: "Book flight", status: "in-preparation" }
];
const inputId = _.uniqueId("form-input-");
// function component
const List = () => {
const [value, setValue] = React.useState(""); // Hook
const [list, setList] = React.useState(initialList); // Hook
const handleChange = event => {
setValue(event.target.value);
};
// add element to the list
const handleSubmit = event => {
// prevent to add empty list elements
if (value) {
setList(
list.concat({ id: Date.now(), name: value, status: "in-preparation" })
);
}
// clear input value after added new element to the list
setValue("");
event.preventDefault();
};
// remove current element from the list
const removeListItem = id => {
setList(list.filter(item => item.id !== id));
};
// adding status to current element of the list
const setListItemStatus = (id, status) => {
setList(
list.map(item => (item.id === id ? { ...item, status: status } : item))
);
};
return (
<div className="to-do-list-wrapper">
<form className="to-do-form" onSubmit={handleSubmit}>
<label htmlFor={inputId} className="to-do-form-label">
Type item name:
</label>
<input
type="text"
value={value}
onChange={handleChange}
className="to-do-form-input"
id={inputId}
/>
<button type="submit" className="button to-do-form-button">
Add Item
</button>
</form>
<ul className="to-do-list">
{list.map(item => (
<ListItem
name={item.name}
status={item.status}
key={item.id}
id={item.id}
setListItemStatus={setListItemStatus}
removeListItem={removeListItem}
></ListItem>
))}
</ul>
</div>
);
};
export default List;
And ListItem.js file:
import React from "react";
import PropTypes from "prop-types";
import "./ListItem.scss";
const ListItem = props => {
return (
<li className={"list-item " + props.status} key={props.id}>
<span className="list-item-icon"></span>
<div className="list-item-content-wrapper">
<span className="list-item-text">{props.name}</span>
<div className="list-item-button-wrapper">
<button
type="button"
onClick={() => props.setListItemStatus(props.id, "in-preparation")}
className="button list-item-button"
>
In preparation
</button>
<button
type="button"
onClick={() => props.setListItemStatus(props.id, "in-progress")}
className="button list-item-button"
>
In progress
</button>
<button
type="button"
onClick={() => props.setListItemStatus(props.id, "done")}
className="button list-item-button"
>
Done
</button>
<button
type="button"
onClick={() => props.removeListItem(props.id)}
className="button list-item-button"
>
Remove
</button>
</div>
</div>
</li>
);
};
ListItem.propTypes = {
id: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
name: PropTypes.string,
status: PropTypes.string,
removeListItem: PropTypes.func,
setListItemStatus: PropTypes.func
};
export default ListItem;
Props are per component.
The purpose of prop types is to let you make some type checking on a component's props.
It seems like you did it correctly in the ListItem component.
Basically, you just list all of the component's props and what type they should be.
Like you did here:
ListItem.propTypes = {
id: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
name: PropTypes.string,
status: PropTypes.string,
removeListItem: PropTypes.func,
setListItemStatus: PropTypes.func
};
I am not exactly sure where will the propTypes shape and arrayOf be used, but generally, PropTypes are useful when you are trying to render a child component within a parent component, and you want to carry out TypeChecking on the props of the relative child component.
In your scenario, arrayOf and shape can be used in one of the props within your component whereby the prop is an array of a certain type (arrayOf), and an object of a certain type(shape, or exact).

Displaying Multiple API Responses in React

I am learning React and I have a solution that requests information through an API, and when there is a response it sets the state, however when rendering my results it only shows the last response on screen,
Even though there are 4, see image below.
App.js
import React from 'react';
import Tiles from './components/Tiles'
import Form from './components/Form'
import WaterData from './components/WaterData'
class App extends React.Component{
state = {
station_name: undefined,
water_value: undefined,
dateTime: undefined
}
getData = async (e) => {
e.preventDefault();
const name = e.target.elements.name.value;
const api_call = await fetch(`https://waterlevel.ie/geojson/latest/`)
.then(response1 => {
response1.json().then(data =>{
Array.from(data.features).forEach(element => {
if(element.properties['station.name'] === name){
this.setState({
station_name: element.properties['station.name'],
water_value: element.properties['value'],
dateTime: element.properties['datetime'],
});
}
})
});
});
}
render(){
return(
<div>
<Tiles />
<Form loadData={this.getData}/>
<WaterData
station_name={this.state.station_name}
water_value={this.state.water_value}
dateTime={this.state.dateTime}
/>
</div>
)
}
}
export default App;
WaterData.js
import React from 'react';
const Weather = (props) => {
console.log(props)
return(
<li>
<p>Location {props.station_name}</p>
<p>Value {props.water_value}</p>
<p>Date Time: {props.dateTime}</p>
</li>
)
}
export default Weather;
Can someone explain to me why the 4 responses do not display?
This happens because you are replacing the values in your state for each part of your data.
You can filter out the element you want in your array using filter.
And then put the whole array into your state only once :
const api_call = await fetch(`https://waterlevel.ie/geojson/latest/`)
.then(response1 => {
response1.json().then(data => {
const features = Array.from(data.features)
.filter(el => el.properties['station.name'] === name);
this.setState({ features });
})
});
But now, to render all of them, you will need to map your state values :
render(){
return(
<div>
<Tiles />
<Form loadData={this.getData}/>
{this.state.features.map(feat => <WaterData
key={/* Find something unique*/}
station_name={feat.properties['station.name']}
water_value={feat.properties['value']}
dateTime={feat.properties['datetime']}
/>)}
</div>
)
}
There's no need to store all the value separately in your state if they are related to each other, it would be fine for your child component though.
To be sure that the state value is always an array, give it an empty array at the start of your class :
state = {
features: []
}

Categories

Resources