update the view in a funcional component reactjs - reactjs

I have two components contact_list.jsx and contact.jsx and a model contact.class and I can not update the view of props conected.
contact_list.
const ContactListComponent = ({contact }) => {
const defaultContact = new Contact('pepe', 'perez','perez#perez.com',
);
/**
*
*
*/
const [conected, setconected] = useState(false);
const changeState = (changeState) =>
{
console.log(conected);
setconected(conected ===false);
}
useEffect( () => {
console.log('se ejecuta');
},[conected]);
return (
<div>
<div>
your Contacts:
</div>
<ContactComponent conected={conected} contact={defaultContact} changeState={changeState}></ContactComponent>
<div>
<button onClick={changeState}>
cambiar estado conectado
</button>
</div>
</div>
)
}
export default ContactListComponent;
contact.jsx
const ContactComponent = ({contact}) => {
return (
<div>
<h2>
nombre: { contact.name }
</h2>
<h3>
descripción: { contact.surname }
</h3>
<h4>
level: { contact.email }
</h4>
<h5>
this state is: {contact.conected ? 'disponible': 'no disponible'}
</h5>
</div>
);
}
ContactComponent.propTypes = {
contact: PropTypes.instanceOf(Contact),
}
contact.class
export class Contact {
name = '';
surname = '';
email = '';
conected = false;
// level = LEVELS.NORMAL;
constructor(name, surname, email, conected) {
this.name = name;
this.surname = surname;
this.email = email;
this.conected = conected;
}
}
export default ContactComponent;
app.js
function App() {
return (
<div className="App">
<header className="App-header">
<img src={logo} className="App-logo" alt="logo" />
{/* <TaskListComponent></TaskListComponent> */}
<ContactListComponent></ContactListComponent>
</header>
</div>
);
}
export default App;
I dont know how to update with the button contact.conected from 'disponible' to 'no disponible'. in contact_list.jsx I have the hooks useState and useEffect I can change the conected props from true to false but I can not update the view.

I noticed some problems in your code.
You are initializing a "connected" state within the contact list component.
Although you are passing that state to the contact component by props, you are never using it inside it, always referencing contact.connected.
The use of the useEffect is not necessary at all.
I tried to replicate your functionality, in a cleaner way, but with the expected behavior:
import React, { useState } from "react";
const Contact = ({ contact }) => (
<div>
<h2>Nombre: {contact.name}</h2>
<h3>Apellido: {contact.surname}</h3>
<h4>Email: {contact.email}</h4>
<h5>Status: {contact.connected ? "Disponible" : "No disponible"}</h5>
</div>
);
const ContactsList = () => {
const [contacts, setContacts] = useState([{
name: 'Pepe',
surname: 'Pérez',
email: 'pepe#perez.com',
connected: false
}]);
const handleConnectedToggle = (index) => {
setContacts(prevState => {
const newContacts = [...prevState];
newContacts[index] = {
...prevState[index],
connected: !prevState[index].connected
};
return newContacts;
});
}
return (
<div>
{contacts.map((contact, index) => (
<div key={index}>
<Contact contact={contact} />
<button onClick={() => handleConnectedToggle(index)}>
Cambiar estado
</button>
</div>
))}
</div>
);
};
export default function App() {
return (
<div className="App">
<ContactsList />
</div>
);
}

Related

I want to pass data to parent component foom a child component using props

I'm trying to pass data to the parent component Top.js using props from a child component TagsInput.js where I can add tags but
I don't understand what is causing the error...
What I want to achieve
I want to pass "tags" to the parent component Top.js from TagsInput.js in the child component with props.
I got the error like
props.setTagsinput is not a function
TagsInput.js
import React from "react";
const TagsInput = (props) => {
//1, Define the tags variable to store the entered tags. (I want to pass the value of the tags variable to the parent component Top.js)
const [tags, setTags] = React.useState([]);
//2, Put formText in the function received from the parent component and return it.
props.setTagsinput(tags);
console.log(props)
let tag_list = []
tag_list.push(tags);
const addTags = event => {
if (event.key === "Enter" && event.target.value !== "") {
setTags([...tags, event.target.value]);
event.target.value = "";
}
};
const removeTags = index => {
setTags([...tags.filter(tag => tags.indexOf(tag) !== index)]);
};
return (
<div className="tags-input">
<div className="tags_section">
{tags.map((tag, index) => (
<div className="tag tag-flex" key={index}>
<p className="tag-p">{tag}</p>
</div>
))}
</div>
<input
type="text"
onKeyUp={event => addTags(event)}
placeholder="Press enter to add tags"
/>
</div>
);
};
export default TagsInput;
Top.js
import React, {useState, useEffect} from 'react';
import axios from 'axios';
import Student from './Student';
import TagsInput from "./TagsInput";
const Top = () => {
const [ posts, setPosts] = useState([]);
const [ allPosts, setAllPosts] = useState([]);
let tag_list = []
const [searchKeyword, setSearchKeyword] = React.useState("");
const [searchTagKeyword, setTagSearchKeyword] = React.useState("");
console.log(searchKeyword)
const[tags_from_tagsinput, setTagsinput]= useState("");
console.log(tags_from_tagsinput);
useEffect(() => {
axios.get('xxx.com')
.then(result => {
setPosts(result.data.students);
setAllPosts(result.data.students);
if (searchKeyword) {
getSearchResult()
}
})},
[searchKeyword]);
const getSearchResult = () => {
console.log(searchKeyword)
const result = allPosts.filter((output, index) => {
return output.firstName.toLowerCase().includes(searchKeyword.toLowerCase())||output.lastName.toLowerCase().includes(searchKeyword.toLowerCase());
});
console.log(result)
setPosts(result);
};
const getTagSearchResult = () => {
console.log(searchTagKeyword)
const result = allPosts.filter((output, index) => {
return output.lastName.toLowerCase().includes(searchTagKeyword.toLowerCase());
});
console.log(result)
setPosts(result);
};
return (
<div>
<TagsInput setTagsinput={setTagsinput}/>
<div>
<input className="search-box" placeholder="" value={searchKeyword} onChange={(e) => setSearchKeyword(e.target.value)}/>
</div>
<div>
<input className="search-box" placeholder="" value={searchTagKeyword} onChange={(e) => setSearchKeyword(e.target.value)}/>
</div>
<div>
{searchKeyword &&
<p>{searchKeyword} Search</p>
}
{posts ?
<>
{posts.map((data, i) =>
<Student data={data} />
)}
</>
:
<div>
<p>Not Found!</p>
</div>
}
</div>
</div>
);
}
export default Top;
Student.js
import React, {useState} from 'react';
import TagsInput from './TagsInput';
const Student = (props) => {
const [show, setShow] = useState(false)
const gradesAverage = (grades) => {
let sum = 0;
grades.forEach(function(score) {
sum += Number(score);
});
let ave = sum / grades.length
return ave;
};
return (
<div className="flex">
<div className="image">
<img src={props.data.pic} className="profile" />
</div>
<div>
<p className="name">{props.data.firstName} {props.data.lastName}</p>
<button className="button" onClick={() => setShow(!show)}>
{show? <div className="button_p">-</div>:<div className="button_p">+</div>}
</button>
<div className="info">
<p>Email: {props.data.email}</p>
<p>Company: {props.data.company}</p>
<p>Skill: {props.data.skill}</p>
<p>Average Grade: {gradesAverage(props.data.grades)}%</p>
{show &&
<>
<p>Test 1: {props.data.grades[0]}%</p>
<p>Test 2: {props.data.grades[1]}%</p>
<p>Test 3: {props.data.grades[2]}%</p>
<p>Test 4: {props.data.grades[3]}%</p>
<p>Test 5: {props.data.grades[4]}%</p>
<p>Test 6: {props.data.grades[5]}%</p>
<p>Test 7: {props.data.grades[6]}%</p>
<p>Test 8: {props.data.grades[7]}%</p>
</>
}
<TagsInput />
</div>
</div>
</div>
);
}
export default Student;
You can not directly use one component hook declaration in another component, you need to have a callback function to update that state. I modified your code to use the top page setTagsinput in student tag input
Top.js
import React, { useState, useEffect } from "react";
import axios from "axios";
import Student from "./Student";
import TagsInput from "./TagsInput";
const Top = () => {
const [posts, setPosts] = useState([]);
const [allPosts, setAllPosts] = useState([]);
let tag_list = [];
const [searchKeyword, setSearchKeyword] = React.useState("");
const [searchTagKeyword, setTagSearchKeyword] = React.useState("");
console.log(searchKeyword);
const [tags_from_tagsinput, setTagsinput] = useState("");
console.log(tags_from_tagsinput);
useEffect(() => {
axios.get("xxx.com").then((result) => {
setPosts(result.data.students);
setAllPosts(result.data.students);
if (searchKeyword) {
getSearchResult();
}
});
}, [searchKeyword]);
const getSearchResult = () => {
console.log(searchKeyword);
const result = allPosts.filter((output, index) => {
return (
output.firstName.toLowerCase().includes(searchKeyword.toLowerCase()) ||
output.lastName.toLowerCase().includes(searchKeyword.toLowerCase())
);
});
console.log(result);
setPosts(result);
};
const getTagSearchResult = () => {
console.log(searchTagKeyword);
const result = allPosts.filter((output, index) => {
return output.lastName
.toLowerCase()
.includes(searchTagKeyword.toLowerCase());
});
console.log(result);
setPosts(result);
};
const setTagsFromStudent = (tags) => {
setTagsinput(tags);
};
return (
<div>
<div>
<input
className="search-box"
placeholder=""
value={searchKeyword}
onChange={(e) => setSearchKeyword(e.target.value)}
/>
</div>
<div>
<input
className="search-box"
placeholder=""
value={searchTagKeyword}
onChange={(e) => setSearchKeyword(e.target.value)}
/>
</div>
<div>
{searchKeyword && <p>{searchKeyword} Search</p>}
{posts ? (
<>
{posts.map((data, i) => (
<Student data={data} setStudentTags={setTagsFromStudent} />
))}
</>
) : (
<div>
<p>Not Found!</p>
</div>
)}
</div>
</div>
);
};
export default Top;
Student.js
import React, { useState } from "react";
import TagsInput from "./TagsInput";
const Student = (props) => {
const [show, setShow] = useState(false);
const gradesAverage = (grades) => {
let sum = 0;
grades.forEach(function (score) {
sum += Number(score);
});
let ave = sum / grades.length;
return ave;
};
return (
<div className="flex">
<div className="image">
<img src={props.data.pic} className="profile" />
</div>
<div>
<p className="name">
{props.data.firstName} {props.data.lastName}
</p>
<button className="button" onClick={() => setShow(!show)}>
{show ? (
<div className="button_p">-</div>
) : (
<div className="button_p">+</div>
)}
</button>
<div className="info">
<p>Email: {props.data.email}</p>
<p>Company: {props.data.company}</p>
<p>Skill: {props.data.skill}</p>
<p>Average Grade: {gradesAverage(props.data.grades)}%</p>
{show && (
<>
<p>Test 1: {props.data.grades[0]}%</p>
<p>Test 2: {props.data.grades[1]}%</p>
<p>Test 3: {props.data.grades[2]}%</p>
<p>Test 4: {props.data.grades[3]}%</p>
<p>Test 5: {props.data.grades[4]}%</p>
<p>Test 6: {props.data.grades[5]}%</p>
<p>Test 7: {props.data.grades[6]}%</p>
<p>Test 8: {props.data.grades[7]}%</p>
</>
)}
{/*pass settag from topTag component*/}
<TagsInput setStudentTags={props.setStudentTags} />
</div>
</div>
</div>
);
};
export default Student;
TagsInput.js
import React from "react";
const TagsInput = (props) => {
const [tags, setTags] = React.useState([]);
let tag_list = [];
tag_list.push(tags);
const addTags = (event) => {
if (event.key === "Enter" && event.target.value !== "") {
setTags([...tags, event.target.value]);
// call function pass down from toptag
props.setStudentTags(tags);
event.target.value = "";
}
};
const removeTags = (index) => {
setTags([...tags.filter((tag) => tags.indexOf(tag) !== index)]);
};
return (
<div className="tags-input">
<div className="tags_section">
{tags.map((tag, index) => (
<div className="tag tag-flex" key={index}>
<p className="tag-p">{tag}</p>
</div>
))}
</div>
<input
type="text"
onKeyUp={(event) => addTags(event)}
placeholder="Press enter to add tags"
/>
</div>
);
};
export default TagsInput;
You should consider exploring React context -https://reactjs.org/docs/context.html, its built exactly for something like this.
You are getting this error because, like you mentioned, TagsInput component is used in Student component but it doesn’t pass the state setter setTagsInput function to the TagsInput component.
Now, assuming you need tags created inside Student and displayed in Top, also assuming that both are rendered in the same parent component, you can create a state for tags in the parent component. This component will pass a state setter function to Student which passes the setter to TagsInput and the state itself to Top to use the list of tags.
Something like:
const App = () => {
const [tags,setTags] = useState([]);
return (<div>
<Top tags={tags} />
<Student setTags={setTags} />
</div>);
}
Your Student component can then pass it to TagsInput like:
const Student = (props) => {
return (<div>
{/* everything else */}
<TagsInput setTagsinput={props.setTags} />
</div>)
}
In your Top component you can create a function that updates your tags_from_tagsinput hook then pass it as props to the child component
import TagsInput from "./TagsInput";
const Top = () => {
const[tags_from_tagsinput, setTagsinput]= useState("");
console.log(tags_from_tagsinput);
const getTag = (value) => {
setTagsinput(value);
};
return (
<div>
<TagsInput getTag={getTag} />
</div>
);
}
export default Top;
Now from your TagsInput component you can call this function to update tags_from_tagsinput of Top, let's suppose that you want to updated when the user click on a button
import React from "react";
const TagsInput = (props) => {
return (
<div className="tags-input">
...
<button onClick={()=>{props.getTag(tags)}}>updated parent component</button>
</div>
);
};
export default TagsInput;

React: filtering a todo list based on button clicked

I'm new to React and currently working on a to-do list app. Currently, I'm able to add, delete and edit the to-do list.
I have a problem filtering my to-do list based on categories. The categories I have are all, active and completed.
I'm stuck trying to filter the selected list based on the button clicked.
App.js
import React from "react";
import "./styles.css";
import "./App.css";
import Header from "./components/Header";
import AddTask from "./components/AddTask";
import Task from "./components/Task";
import Filterbtns from "./components/Filterbtns";
import data from "./data";
import { nanoid } from "nanoid";
const FILTER_MAP = {
All: () => true,
Active: (todo) => !todo.completed,
Completed: (todo) => todo.completed
};
const FILTER_NAMES = Object.keys(FILTER_MAP); //keys
function App() {
const [taskList, setTaskList] = React.useState(data);
const [filtered, setFiltered] = React.useState(data); //state to be filtered
const filteredListName = FILTER_NAMES;
const [activeList, setActiveList] = React.useState(filteredListName[0]); //default list
const taskItems = filtered.map((todo) => {
return (
<Task
id={todo.id}
name={todo.name}
completed={todo.completed}
key={todo.id}
toggleTaskCompleted={toggleTaskCompleted}
deleteTask={deleteTask}
editTask={editTask}
/>
);
});
const taskNoun = taskList.length !== 1 ? "tasks" : "task";
const headingText = `${taskList.length} ${taskNoun} remaining`;
function toggleTaskCompleted(id) {
const updatedTasks = taskList.map((todo) => {
if (id === todo.id) {
return { ...todo, completed: !todo.completed };
}
return todo;
});
setTaskList(updatedTasks);
}
function addTask(name) {
const newTask = { id: nanoid(), name: name, completed: false };
setTaskList([...taskList, newTask]);
}
function deleteTask(id) {
const remTasks = taskList.filter((todo) => id !== todo.id);
setTaskList(remTasks);
}
function editTask(id, newName) {
const editTaskList = taskList.map((todo) => {
if (id === todo.id) {
return { ...todo, name: newName };
}
return todo;
});
setTaskList(editTaskList);
}
return (
<div className="App">
<Header />
<AddTask addTask={addTask} />
<div>
<div className="task--list-btn">
<Filterbtns
taskList={taskList}
setFiltered={setFiltered}
filteredListName={filteredListName}
activeList={activeList}
setActiveList={setActiveList}
/>
<div className="task--lst">
<h2>TASKS</h2>
<h3>{headingText}</h3>
{taskItems}
</div>
</div>
<div>No task Available</div>
</div>
</div>
);
}
export default App
Filterbtns.js
import React from "react";
export default function Filterbtns(props) {
React.useEffect(() => {
if (props.activeList) {
props.setActiveList(props.filteredListName[0]);
console.log("try");
return;
}
const filtered = props.taskList.filter((todo) =>
todo.includes(props.activeList)
);
props.setFiltered(filtered);
}, [props.activeList]);
return (
<div className="task--btns">
<button
className="all-tasks inputs"
onClick={() => props.setActiveList(props.FilterbtnsfilteredListName[0])}
>
ALL
</button>
<br />
<button
className="active-tasks inputs"
onClick={() => props.setActiveList(props.filteredListName[1])}
>
ACTIVE
</button>
<br />
<button
className="completed-tasks inputs"
onClick={() => props.setActiveList(props.filteredListName[2])}
>
COMPLETED
</button>
</div>
);
}
I've not checked but from what it looks like React.useEffect is redundant inside Filterbtns and you need to pass down FilterbtnsfilteredListName to Filterbtns as props like this:
<Filterbtns
taskList={taskList}
setFiltered={setFiltered}
filteredListName={filteredListName}
activeList={activeList}
setActiveList={setActiveList}
FilterbtnsfilteredListName={filteredListName} // you forgot this
/>
Although if I can change the logic a bit, a better composition would be:
const FILTER_MAP = {
All: () => true,
Active: (todo) => !todo.completed,
Completed: (todo) => todo.completed
};
const FILTER_NAMES = Object.keys(FILTER_MAP); //keys
export default function App() {
const [taskList, setTaskList] = useState(data);
const [currentFilter, setCurrentFilter] = useState(FILTER_NAMES[0])
const filtered = taskList.filter(FILTER_MAP[currentFilter])
const taskItems = filtered.map((todo) => {
...
});
...
return (
<div className="App">
<Header />
<AddTask addTask={addTask} />
<div>
<div className="task--list-btn">
{/* IMPORTANT: FilterButton new API */}
<FilterButton
filterNames={FILTER_NAMES}
onFilter={setCurrentFilter}
/>
<div className="task--lst">
<h2>TASKS</h2>
<h3>{headingText}</h3>
{taskItems}
</div>
</div>
<div>No task Available</div>
</div>
</div>
);
}
function FilterButton(props) {
return (
<div className="task--btns">
{props.filterNames.map((filterName) => {
return <button
className={`${filterName}-tasks inputs`}
onClick={() => props.onFilter(filterName)}
>
{filterName}
</button>
})}
</div>
)
}
Happy React journey! you are doing great.

Page jumps when useState updates

I'm trying to build a clone of a MovieDataBase site in react, and when I update my state variable the page jumps ( it jumps up exactly 60px, for whatever reason, and then back down again each time I toggle the switch). I thought it was maybe because I'm interacting with the DOM to get the toggle switch to work, but that doesn't seem to be the issue. I've also been told that happens with styled components, which I don't think I have (still pretty new to REACT, so maybe???). Anyway - I can't figure out why this is happening. I've included the code for the component in question below.
import React, { useState, useEffect } from "react";
import axios from "axios";
import API_KEY from "../../config";
const Popular = ({ imageUri }) => {
// GET POPULAR MOVIES
const [popularMovies, setPopularMovies] = useState("");
const [genre, setGenre] = useState("movie");
const getPopular = async () => {
const response = await axios.get(
`https://api.themoviedb.org/3/discover/${genre}?sort_by=popularity.desc&api_key=${API_KEY}`
);
setPopularMovies(response.data.results);
};
useEffect(() => {
getPopular();
}, [genre]);
const listOptions = document.querySelectorAll(".switch--option");
const background = document.querySelector(".background");
const changeHandler = (el) => {
let getGenre = el.target.dataset.genre;
setGenre(getGenre);
listOptions.forEach((option) => {
option.classList.remove("selected");
});
el = el.target.parentElement.parentElement;
let getStartingLeft = Math.floor(
listOptions[0].getBoundingClientRect().left
);
let getLeft = Math.floor(el.getBoundingClientRect().left);
let getWidth = Math.floor(el.getBoundingClientRect().width);
let leftPos = getLeft - getStartingLeft;
background.setAttribute(
"style",
`left: ${leftPos}px; width: ${getWidth + 1}px`
);
el.classList.add("selected");
};
const isMovieSelected = genre === "movie";
const isTvSelected = genre === "tv";
const movieData = "movie";
const tvData = "tv";
return (
<section className="container movie-list">
<div className="flex flex--align-center">
<MovieHeader>What's Popular</MovieHeader>
<div className="switch flex">
<Toggle
onChange={changeHandler}
selected={isMovieSelected}
data={movieData}
>
In Theaters
<div className="background"></div>
</Toggle>
<Toggle
onChange={changeHandler}
selected={isTvSelected}
data={tvData}
>
On TV
</Toggle>
</div>
</div>
<div className="scroller">
<div className="flex flex--justify-center">
<div className="flex flex--nowrap container u-overScroll">
{popularMovies &&
popularMovies.map((movie) => {
const { title, id, poster_path } = movie;
return (
<MovieItem
title={title}
imageUri={imageUri}
key={id}
poster_path={poster_path}
/>
);
})}
</div>
</div>
</div>
</section>
);
};
export default Popular;
const Toggle = (props) => {
const { children, onChange, selected, data } = props;
const className = selected ? "switch--option selected" : "switch--option";
return (
<div className={className}>
<h3>
<a data-genre={data} onClick={onChange} className="switch--anchor">
{children}
</a>
</h3>
</div>
);
};
const MovieHeader = (props) => {
const { children } = props;
return (
<div className="movie-list__header">
<h3>{children}</h3>
</div>
);
};
const MovieItem = (props) => {
const { title, imageUri, poster_path, id } = props;
return (
<div key={id} className="card">
<div className="image">
<img src={imageUri + "w500" + poster_path} />
</div>
<p>{title}</p>
</div>
);
};

How to pass the useReducer State to the child component?

I have a simple feedback application that contains multiple component.
app.js - parent component
data.js - this contain a dummy data the have name and the listofreview
component
user - this component will display the name and the thumbnail
review - this will display the list of review about the user and also have button that say leave a review.
modal - after the user click the leave a review this modal will appear that have a list of element that will update the list of review
I used useReducer to update the state. But the problem is the review.js don't show the updated state. maybe because the useReducer is located on the modal.js. What should i do so that i can also update the data that been display on the review.js
App.js
function App() {
return (
<div className="main-container">
<DisplayUser />
<div className="mainContent-container">
<DisplayReview />
</div>
</div>
);
}
User.js
import { data } from '../../src/data';
const CommentHandler = () => {
const [user] = React.useState(data);
return (
<>
{user.map((person) => {
const { id, name, thumbnail } = person;
return (
<div key={id} className='user-container'>
<h2 className="user-name">{name}</h2>
<img src={thumbnail} alt={name} title={name} className='user-icon' />
</div>
);
})}
</>
);
};
Review.js
import DisplayModal from './Modal'
import Modal from 'react-modal';
import { useState } from 'react';
import { FontAwesomeIcon } from '#fortawesome/react-fontawesome'
import { data } from '../../src/data';
const ReviewHandler = () => {
const [user] = useState(data);
const [showModal, setShowModal] = useState(false);
return (
<>
{user.map((person) => {
const { listOfReview } = person;
return (
<div key={person.id} className='review-container active'>
<div className="content-container">
{listOfReview.map((sub) => {
const { reviewId, rating, name, occupation, review } = sub;
return (
<div key={reviewId} className="content-container">
<div className='reviewer-rating'>
<div className="static-review">
{[...Array(5)].map((star, index) => {
return <FontAwesomeIcon key={index} className="star" icon="star" />
})}
</div>
<div className="dynamic-review">
{[...Array(rating)].map((star, index) => {
return <FontAwesomeIcon key={index} className="star" icon="star" />
})}
</div>
</div>
<div className="user-description">
<h3>{occupation}</h3>
<h4>{name}</h4>
<p>{review}</p>
</div>
</div>
)
})}
<button className="submit" onClick={() => setShowModal(true)}>LEAVE AREVIEW</button>
</div>
</div>
);
})}
<Modal isOpen={showModal} ariaHideApp={false}>
<DisplayModal onClick={(value) => setShowModal(value)} />
</Modal>
</>
);
};
Modal.js
import { useState, useReducer } from 'react';
import { FontAwesomeIcon } from '#fortawesome/react-fontawesome'
import { data } from '../../src/data';
import { reducer } from './reducer';
const ModalHandler = props => {
const [rating, setRating] = useState(null);
const [name, setName] = useState('');
const [occupation, setOccupation] = useState('');
const [reviews, setReviews] = useState('');
const [state, dispatch] = useReducer(reducer, data);
const [hoverRating, setHoverRating] = useState(null);
const handelSubmit = (e) => {
e.preventDefault();
if (name && occupation && reviews) {
const newReview = { reviewId: new Date().getTime(), name, occupation, rating, reviews };
dispatch({
type: 'ADD_REVIEW_ITEM',
payload: newReview
});
}
}
return (
<div className="modal-container">
<div className="modal-backdrop">
<form className="modal-inner" onSubmit={handelSubmit}>
<h2>Feel feel to send us your review!</h2>
<div className='revieweRating-container'>
<h3>How was your experience?</h3><p onClick={() => props.onClick(false)}>X</p>
<div className="dynamic-review">
{[...Array(5)].map((star, i) => {
const ratingValue = i + 1;
return (
<label>
<input type="radio"
name="review-star"
value={ratingValue}
onMouseEnter={() => setHoverRating(ratingValue)}
onMouseLeave={() => setHoverRating(ratingValue)}
onClick={() => setRating(ratingValue)}>
</input>
<FontAwesomeIcon
icon="star"
onMouseEnter={() => setHoverRating(ratingValue)}
onMouseLeave={() => setHoverRating(ratingValue)}
color={ratingValue <= (hoverRating || rating) ? "#FAD020" : "#BCC5D3"}
className="review-star" />
</label>
)
})}
</div>
</div>
<input
type="text"
name="name"
className="name"
placeholder="Name"
value={name}
onChange={(e) => setName(e.target.value)}
/>
<input
type="text"
name="occupation"
className="alioccupationas"
placeholder="Aloccupationias"
value={occupation}
onChange={(e) => setOccupation(e.target.value)}
/>
<textarea
name="review"
cols="30"
rows="6"
className="review"
placeholder="Enter your review here!"
value={reviews}
onChange={(e) => setReviews(e.target.value)}>
</textarea>
<button type="submit" className="submit">SEND A REVIEW</button>
</form>
<div>
{state.map((data) => (
<div key={data.id}>
{data.listOfReview.map((review) => (
<div key={review.reviewId}>
<h3>{review.name}</h3>
<p>{review.occupation}</p>
</div>
))}
</div>
))}
</div>
</div>
</div >
);
}
reducer.js
export const reducer = (state, action) => {
switch (action.type) {
case "ADD_REVIEW_ITEM":
console.log(state);
return state.map((data) => {
if (data) {
const newReview = [...data.listOfReview, action.payload];
return {
...data,
listOfReview: newReview
};
}
return data;
});
default:
return state;
}
};
data.js
export const data = [
{
id: 1607089645363,
name: 'Andress Bonifacio',
noOfReview: 1,
listOfReview: [
{
reviewId: 1607089645361,
name: 'Juan Dela Cruz',
occupation: 'Father of Phil. Revolution',
rating: 5,
review: 'Numquam labore or dolorem enim but accusantium and autem ratione.',
}
]
}
];
If you want to get the updated data across all component, then make sure to have this line of code const [state, dispatch] = useReducer(reducer, data); available in every component that use them like in:-
in User.js
import { data } from '../../src/data';
import { reducer } from './reducer';
const CommentHandler = () => {
// not this
const [user] = React.useState(data);
// instead this
const [state, dispatch] = useReducer(reducer, data);
return (
<>
{state.map((person) => {
const { id, name, thumbnail } = person;
return (
<div key={id} className='user-container'>
<h2 className="user-name">{name}</h2>
<img src={thumbnail} alt={name} title={name} className='user-icon' />
</div>
);
})}
</>
);
};
in Review.js
import { useState, useReducer } from 'react';
import { FontAwesomeIcon } from '#fortawesome/react-fontawesome'
import { data } from '../../src/data';
import { reducer } from './reducer';
const ReviewHandler = () => {
// not this
// const [user] = useState(data);
// instead this
const [state, dispatch] = useReducer(reducer, data);
const [showModal, setShowModal] = useState(false);
return (
<>
{state.map((person) => {
const { listOfReview } = person;
return (
<div key={person.id} className='review-container active'>
<div className="content-container">
{listOfReview.map((sub) => {
const { reviewId, rating, name, occupation, review } = sub;
return (
<div key={reviewId} className="content-container">
<div className='reviewer-rating'>
<div className="static-review">
{[...Array(5)].map((star, index) => {
return <FontAwesomeIcon key={index} className="star" icon="star" />
})}
</div>
<div className="dynamic-review">
{[...Array(rating)].map((star, index) => {
return <FontAwesomeIcon key={index} className="star" icon="star" />
})}
</div>
</div>
<div className="user-description">
<h3>{occupation}</h3>
<h4>{name}</h4>
<p>{review}</p>
</div>
</div>
)
})}
<button className="submit" onClick={() => setShowModal(true)}>LEAVE AREVIEW</button>
</div>
</div>
);
})}
<Modal isOpen={showModal} ariaHideApp={false}>
<DisplayModal onClick={(value) => setShowModal(value)} />
</Modal>
</>
);
};
I would suggest you try and use context api instead of just relying on useReducer.

How to pass variables when calling components

I'm building a mini app and I want to get it cleaner.
So basically I want to have 3 components : App, List and Person.
Here is the code :
App.js
import React, { Component } from "react";
import List from './List';
class App extends Component {
constructor(props) {
super(props);
this.state = {
results: [],
search: '',
currentPage: 1,
todosPerPage: 3
};
this.handleClick = this.handleClick.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 (
<List />
);
}
}
export default App;
List.js
import React, { Component } from 'react';
import Person from './Person';
class List extends Component {
render() {
const { results, currentPage, todosPerPage } = this.state;
const indexOfLastTodo = currentPage * todosPerPage;
const indexOfFirstTodo = indexOfLastTodo - todosPerPage;
const currentTodos = results.slice(indexOfFirstTodo, indexOfLastTodo).filter(item => {
return item.name.toLowerCase().indexOf(this.state.search) !== -1;
});
const renderTodos = currentTodos.map((item, index) => {
return (
<Person item={this.state.item} index={this.state.index}/>
);
});
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={this.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={this.state.search}
onChange={this.updateSearch.bind(this)}
/>
</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;
Person.js
import React, { Component } from 'react';
function Person(item, index) {
return (
<div className="col-lg-4 mb-4" key={index}>
<div className="card">
<div className="card-header">
<h4 className="mb-0">{item.name}</h4>
</div>
<div className="card-body">
<h5 className="card-title">Caractéristiques</h5>
<ul>
<li>Année de naissance : {item.birth_year}</li>
<li>Taille : {item.height} cm</li>
<li>Masse : {item.mass}</li>
<li>Couleur des yeux : {item.eye_color}</li>
<li>Couleur de cheveux : {item.hair_color}</li>
<li>Couleur de peau : {item.skin_color}</li>
</ul>
Sa fiche
</div>
</div>
</div>
)
}
export default Person;
My issue is that I get TypeError: Cannot read property 'results' of null when rendering.
Is it possible to have variable go into every file if I define them all in App.js ?
You are not passing the data the correct way. Try this:
In App.js pass to List component the needed data:
render() {
return (
<List data={this.state}/>
);
}
Then in render() method in List.js get the passed data prop, then extract the data from there:
render() {
const { data } = this.props;
const { results, search, currentPage, todosPerPage } = data;
// ...
// in currentTodos function dont use this.state.search but just "search", that we got above from the data variable
// ...
// also your renderTodos should look like this - use the item and index variables
const renderTodos = currentTodos.map((item, index) => {
return (
<Person item={item} index={index}/>
);
});
// ...
}
So your List.js should look like this:
import React, { Component } from 'react';
import Person from './Person';
class List extends Component {
render() {
// get the data
const { data } = this.props;
// get the properties
const { results, search, currentPage, todosPerPage } = data;
const indexOfLastTodo = currentPage * todosPerPage;
const indexOfFirstTodo = indexOfLastTodo - todosPerPage;
const currentTodos = results.slice(indexOfFirstTodo, indexOfLastTodo).filter(item => {
// use "search" variable
return item.name.toLowerCase().indexOf(search) !== -1;
});
const renderTodos = currentTodos.map((item, index) => {
return (
// use item and index
<Person item={item} index={index}/>
);
});
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={this.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} // use search variable here too
onChange={this.updateSearch.bind(this)}
/>
</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;
And your function in Person.js should have the following declaration, because the parameters are extracted from the passed props:
function Person({item, index}) {
// ...
}
You can use pass variables in your props of <List /> component by passing state inside render function of App.js while calling <List /> like this
render() {
//Passing Data inside props
<List data={this.state} />
}
and inside your List.js, You can access the data variable
const { results, currentPage, todosPerPage } = this.props.data;

Resources