How to toggle the icon only if id matches? - javascript

After receiving data from the API then mapping it out - I tried toggling one icon, but it toggles all the icons that were mapped out. I am trying to click one of the icon and not all.
import { FaRegHeart, FaHeart } from "react-icons/fa";
import { IconContext } from 'react-icons';
import { useState, useEffect } from 'react';
const Replies = ({ comments, id }) => {
const [liked, setLiked] = useState(false);
const [replies, setReplies] = useState(null);
useEffect(() => {
const abortCont = new AbortController();
fetch('http://192.168.1.98:5000/blogs/' + id, { signal: abortCont.signal })
.then(res => {
if (!res.ok) {
throw new Error('Could not get data from the database');
}
return res.json()
})
.then(data => setReplies(data))
.catch(err => {
if (err.name === 'AbortError') {
// empty if statement
}})
return () => abortCont.abort()
}, [liked])
const likeComment = () => {liked ? setLiked(false) : setLiked(true)}
return (
<>
{replies && <div className="commentSection">
{
replies.discussion.map((discussion) => (
<div key={discussion.id}>
<div className="commentEmoji">
{
liked ? <IconContext.Provider value={{ color: 'red', className: 'love-icon' }}>
<FaHeart onClick={likeComment} />
</IconContext.Provider>
: <FaRegHeart onClick={likeComment} />}
</div>
</div>
))}
</div>
}
</>
);
}
export default Replies;
This is what the API looks like:
"discussion": [
{
"id": 1,
"liked": false,
"thumbnail": "https://randomuser.me/api/portraits/thumb/men/75.jpg",
"user": "Kayode",
"comment": "They know electric vehicles are the future 👌🏾",
"replies": ""
},
{
"user": "Farook",
"liked": true,
"id": 2,
"thumbnail": "https://randomuser.me/api/portraits/thumb/men/74.jpg",
"comment": "I hope they keep buying. TSLA to that freaking moon 🚀",
"replies": ""
}
]
They have a discussion.liked property and I've been trying to access individual and not both.
If I use:
<FaRegHeart onClick={()=> likeComment(discussion.id)} />
Then
const likeComment = (id) => {
console.log(id) //it logs the exact id of the icon i.e 2
}

Try something like this, and pass appropriate props in LikedButton component
import { FaRegHeart, FaHeart } from "react-icons/fa";
import { IconContext } from 'react-icons';
import { useState, useEffect } from 'react';
const LikedButton = () => {
const [liked, setLiked] = useState(false);
const likeComment = () => {liked ? setLiked(false) : setLiked(true)}
return (
<div >
<div className="commentEmoji">
{
liked ? <IconContext.Provider value={{ color: 'red', className: 'love-icon' }}>
<FaHeart onClick={likeComment} />
</IconContext.Provider>
: <FaRegHeart onClick={likeComment} />}
</div>
</div>
)
}
const Replies = ({ comments, id }) => {
const [replies, setReplies] = useState(null);
useEffect(() => {
const abortCont = new AbortController();
fetch('http://192.168.1.98:5000/blogs/' + id, { signal: abortCont.signal })
.then(res => {
if (!res.ok) {
throw new Error('Could not get data from the database');
}
return res.json()
})
.then(data => setReplies(data))
.catch(err => {
if (err.name === 'AbortError') {
// empty if statement
}})
return () => abortCont.abort()
}, [])
return (
<>
{replies && <div className="commentSection">
{
replies.discussion.map((discussion, index) => (
<LikedButton key={index} />
))}
</div>
}
</>
);
}
export default Replies;

import LikedComment from './LikedComment';
import { useState, useEffect } from 'react';
const Replies = ({ comments, id }) => {
const [error, setError] = useState(false);
const [replies, setReplies] = useState(null);
useEffect(() => {
const abortCont = new AbortController();
fetch('http://192.168.1.98:5000/blogs/' + id, { signal: abortCont.signal })
.then((res) => {
if (!res.ok) {
throw new Error('Could not get data from the database');
}
return res.json();
})
.then((data) => {
setReplies(data);
})
.catch((err) => {
if (err.name === 'AbortError') {
// empty if statement
} else {
setError(true);
}
});
return () => abortCont.abort();
}, [id]);
const likeComment = (id) => {
setReplies({
...replies,
discussion: replies.discussion.map((reply) => {
if (reply.id === id) {
return { ...reply, liked: !reply.liked };
} else {
return reply;
}
}),
});
};
return (
<>
{error && <div className="commentError">Cannot retrieve comments </div>}
{replies && (
<div className="commentSection">
{replies.discussion.map((discussion, index) => (
<div key={discussion.id}>
<div className="comment">
<img className="commentImg" src={discussion.thumbnail} alt="" />
<div className="commentBody">
<p>
{' '}
<span>{discussion.user}</span> {discussion.comment}
</p>
</div>
<div className="commentEmoji">
<LikedComment
liked={discussion.liked}
key={index}
likeComment={() => likeComment(discussion.id)}
/>
</div>
</div>
</div>
))}
</div>
)}
</>
);
};
export default Replies;

Related

Get undefined value from props on onClick event meanwhile the whole props are displaying well

I'm stuck getting undefined value when clicking on Answers "buttons". It has to console.log the id of this button which was generated by nanoid().
Questions.js
import { useState, useEffect } from "react";
import { nanoid } from "nanoid";
import QuestionSet from "./QuestionSet";
import Answers from "./Answers";
export default function Questions() {
const [data, setData] = useState([]);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
useEffect(() => {
fetch(
"https://opentdb.com/api.php?amount=5&category=12&difficulty=easy&type=multiple"
)
.then((response) => {
if (!response.ok) {
throw new Error(
`This is an HTTP error: The status is ${response.status}`
);
}
return response.json();
})
.then((actualData) => {
setData(actualData.results);
setError(null);
})
.catch((err) => {
setError(err.message);
setData(null);
})
.finally(() => {
setLoading(false);
});
}, []);
const listOfQuestions = data.map((item) => {
const allAnswers = [
{ id: nanoid(), isCorrect: false, answer: item.incorrect_answers[0] },
{ id: nanoid(), isCorrect: false, answer: item.incorrect_answers[1] },
{ id: nanoid(), isCorrect: false, answer: item.incorrect_answers[2] },
{ id: nanoid(), isCorrect: true, answer: item.correct_answer },
];
return {
id: nanoid(),
question: item.question,
answers: allAnswers,
};
});
function holdAnswer(id) {
console.log(id);
}
const questionElm = listOfQuestions.map((question, index, i) => {
return (
<section key={index}>
<QuestionSet question={question.question} key={question.id} />
<Answers
answers={question.answers}
isChosen={question.isChosen}
id={question.answers.id}
holdAnswer={() => holdAnswer(question.answers.id)}
/>
</section>
);
});
return (
<main className="quest-box">
<section className="quest-content">{questionElm}</section>
<button className="answer-btn">Check Answers</button>
{loading && <div>Loading data...</div>}
{error && <div>{`There is a problem fetchning data = ${error}`}</div>}
</main>
);
}
QuestionSet.js (component)
export default function QuestionSet(props) {
return (
<section className="quest" key={props.id}>
<p>{props.question}</p>
</section>
);
}
Answers.js (component)
export default function Answers(props) {
const styles = {
backgroundColor: props.isChosen ? "#D6DBF5" : "transparent",
};
return (
<section className="answer-container">
<div
className="answer-div"
style={styles}
id={props.answers[3].id}
onClick={props.holdAnswer}
>
<p>{props.answers[3].answer}</p>
</div>
<div
className="answer-div"
style={styles}
id={props.answers[1].id}
onClick={props.holdAnswer}
>
<p>{props.answers[1].answer}</p>
</div>
<div
className="answer-div"
style={styles}
id={props.answers[2].id}
onClick={props.holdAnswer}
>
<p>{props.answers[2].answer}</p>
</div>
<div
className="answer-div"
style={styles}
id={props.answers[0].id}
onClick={props.holdAnswer}
>
<p>{props.answers[0].answer}</p>
</div>
</section>
);
}
Other props are displayed well such as questions and answer text. Also, I tested if the id of questions will be displaying and it works, but not the ids of the answers.
Why does it happen? Why does the part of allAnswers array are showing but the part with ids doesn't? :(
You are doing a few things wrong.
There is no question.answers.id it is an array, what you want is question.answers[0].id
Even with 1 sorted out, the way you handle the click is wrong, you need to just pass the function down to the child component, and let the child component handle it by itself
This is how your code will look like
Questions.js
import { useState, useEffect } from "react";
import { nanoid } from "nanoid";
import QuestionSet from "./QuestionSet";
import Answers from "./Answers";
export default function Questions() {
const [data, setData] = useState([]);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
useEffect(() => {
fetch(
"https://opentdb.com/api.php?amount=5&category=12&difficulty=easy&type=multiple"
)
.then((response) => {
if (!response.ok) {
throw new Error(
`This is an HTTP error: The status is ${response.status}`
);
}
return response.json();
})
.then((actualData) => {
setData(actualData.results);
setError(null);
})
.catch((err) => {
setError(err.message);
setData(null);
})
.finally(() => {
setLoading(false);
});
}, []);
const listOfQuestions = data.map((item) => {
const allAnswers = [
{ id: nanoid(), isCorrect: false, answer: item.incorrect_answers[0] },
{ id: nanoid(), isCorrect: false, answer: item.incorrect_answers[1] },
{ id: nanoid(), isCorrect: false, answer: item.incorrect_answers[2] },
{ id: nanoid(), isCorrect: true, answer: item.correct_answer }
];
return {
id: nanoid(),
question: item.question,
answers: allAnswers
};
});
function holdAnswer(id) {
console.log(id);
}
const questionElm = listOfQuestions.map((question, index, i) => {
return (
<section key={index}>
<QuestionSet question={question.question} key={question.id} />
<Answers
answers={question.answers}
isChosen={question.isChosen}
holdAnswer={holdAnswer}
/>
</section>
);
});
return (
<main className="quest-box">
<section className="quest-content">{questionElm}</section>
<button className="answer-btn">Check Answers</button>
{loading && <div>Loading data...</div>}
{error && <div>{`There is a problem fetchning data = ${error}`}</div>}
</main>
);
}
Answers.js
export default function Answers(props) {
console.log(props);
const styles = {
backgroundColor: props.isChosen ? "#D6DBF5" : "transparent"
};
return (
<section className="answer-container">
<div
className="answer-div"
style={styles}
id={props.answers[3].id}
onClick={() => props.holdAnswer(props.answers[3].id)}
>
<p>{props.answers[3].answer}</p>
</div>
<div
className="answer-div"
style={styles}
id={props.answers[1].id}
onClick={() => props.holdAnswer(props.answers[3].id)}
>
<p>{props.answers[1].answer}</p>
</div>
<div
className="answer-div"
style={styles}
id={props.answers[2].id}
onClick={() => props.holdAnswer(props.answers[3].id)}
>
<p>{props.answers[2].answer}</p>
</div>
<div
className="answer-div"
style={styles}
id={props.answers[0].id}
onClick={() => props.holdAnswer(props.answers[3].id)}
>
<p>{props.answers[0].answer}</p>
</div>
</section>
);
}
Here is a working solution
https://codesandbox.io/s/vigilant-silence-tg43w0?file=/src/Answers.js:0-1116
Just to mention, there are a few optimization to be done for the code, but since it doesn't affect the question, I left it out, we can pick that off in the comment section

useState defaults appear to rerun after running function on state change, defaults shouldnt run twice

I have the following issue with website where the settings state resets after running more searches. The settings component is show below in the picture, it usually works but if you uncheck a box and then run a few more searches at some point the showAllDividends setting will be set to false, the All dividends component won't be on the screen, but for some reason the checkbox itself is checked (true). This is my first time really working with checkboxes in React, and I think I'm using the onChange feature wrong. Right now I get the event.target.checked boolean, but only onChange.
If that isn't the issue then the most likely cause is the default statements being run again on another render:
const [showMainInfo, setShowMainInfo] = useState(true);
const [showYieldChange, setShowYieldChange] = useState(true);
const [showAllDividends, setShowAllDividends] = useState(true);
the thing is I don't see why the default statements would run more than once, the component isn't being destroyed there's no react router usage. I expected it to keep its current state after the page is first loaded. I think the settings defaults are being rerun, but just don't understand why they would.
I unchecked, checked, unchecked the 'All dividends' checkbox, and it was unchecked when I ran 2 more searches. After the second search the checkbox was checked but the component was gone, because showAllDividends was false
main component SearchPage.js:
import React, {useState, useEffect} from 'react';
import { connect } from 'react-redux';
import axios from 'axios';
import SearchBar from './SearchBar';
import AllDividendsDisplay from './dividend_results_display/AllDividendsDisplay';
import DividendResultsDisplay from './dividend_results_display/DividendResultsDisplay';
import SettingsView from './settings/SettingsView';
const HOST = process.env.REACT_APP_HOSTNAME
const PROTOCOL = process.env.REACT_APP_PROTOCOL
const PORT = process.env.REACT_APP_PORT
const BASE_URL = PROTOCOL + '://' + HOST + ':' + PORT
const SearchPage = ({userId}) => {
const DEFAULT_STOCK = 'ibm';
const [term, setTerm] = useState(DEFAULT_STOCK);
const [debouncedTerm, setDebouncedTerm] = useState(DEFAULT_STOCK);
const [loading, setLoading] = useState(false);
const [recentSearches, setRecentSearches] = useState([DEFAULT_STOCK]);
const [dividendsYearsBack, setDividendsYearsBack] = useState('3');
const [debouncedDividendYearsBack, setDebouncedDividendYearsBack] = useState('3');
const [errorMessage, setErrorMessage] = useState('');
const [dividendsData, setDividendsData] = useState(
{
current_price: '',
recent_dividend_rate: '',
current_yield: '',
dividend_change_1_year: '',
dividend_change_3_year: '',
dividend_change_5_year: '',
dividend_change_10_year: '',
all_dividends: [],
name: '',
description: '',
}
)
const [settingsViewVisible, setSettingsViewVisible] = useState(false);
const [showMainInfo, setShowMainInfo] = useState(true);
const [showYieldChange, setShowYieldChange] = useState(true);
const [showAllDividends, setShowAllDividends] = useState(true);
const onTermUpdate = (term) => {
setTerm(term)
}
// TODO: write a custom hook that debounces taking the term and the set debounced term callback
useEffect(() => {
const timerId = setTimeout(() => {
setDebouncedTerm(term);
}, 1500);
return () => {
clearTimeout(timerId);
};
}, [term]);
useEffect(() => {
const timerId = setTimeout(() => {
setDebouncedDividendYearsBack(dividendsYearsBack);
}, 1500);
return () => {
clearTimeout(timerId);
};
}, [dividendsYearsBack]);
useEffect(() => {runSearch()}, [debouncedTerm]);
useEffect(() => {
// alert(dividendsYearsBack)
if (dividendsYearsBack !== '') {
runSearch();
}
}, [debouncedDividendYearsBack])
useEffect(() => {
console.log("user id changed")
if (userId) {
const user_profile_api_url = BASE_URL + '/users/' + userId
axios.get(user_profile_api_url, {})
.then(response => {
const recent_searches_response = response.data.searches;
const new_recent_searches = [];
recent_searches_response.map(dict => {
new_recent_searches.push(dict.search_term)
})
setRecentSearches(new_recent_searches);
})
.catch((error) => {
console.log("error in getting user profile: ", error.message)
})
}
}, [userId])
useEffect(() => {
const user_profile_api_url = BASE_URL + '/users/' + userId
const request_data = {searches: recentSearches}
axios.post(user_profile_api_url, request_data)
// .then(response => {
// console.log(response)
// })
}, [recentSearches])
const makeSearchApiRequest = () => {
let dividends_api_url = BASE_URL + '/dividends/' + term + '/' + dividendsYearsBack
if (!recentSearches.includes(term)) {
setRecentSearches([...recentSearches, term])
}
axios.get(dividends_api_url, {})
.then(response => {
// console.log(response)
setLoading(false);
setDividendsData(response.data);
})
.catch((error) => {
console.log(error.message);
setLoading(false);
setErrorMessage(error.message);
})
}
const runSearch = () => {
console.log("running search: ", term);
setErrorMessage('');
if (term) {
setLoading(true);
if (!dividendsYearsBack) {
setDividendsYearsBack('3', () => {
makeSearchApiRequest()
});
} else {
makeSearchApiRequest()
}
}
}
const recentSearchOnClick = (term) => {
setTerm(term);
setDebouncedTerm(term);
}
const removeRecentSearchOnClick = (term) => {
const searchesWithoutThisOne = recentSearches.filter(search => search !== term)
setRecentSearches(searchesWithoutThisOne);
}
const dividendsYearsBackOnChange = (text) => {
setDividendsYearsBack(text);
}
const renderMainContent = () => {
if (!debouncedTerm) {
return (
<div className="ui active">
<div className="ui text">Search for info about a stock</div>
</div>
)
}
if (loading === true) {
return (
<div className="ui active dimmer">
<div className="ui big text loader">Loading</div>
</div>
)
}
if (errorMessage) {
return (
<div className="ui active">
<div className="ui text">{errorMessage}</div>
</div>
)
} else {
return (
<DividendResultsDisplay
data={dividendsData}
dividends_years_back={dividendsYearsBack}
dividendsYearsBackOnChange={dividendsYearsBackOnChange}
showMainInfo={showMainInfo}
showYieldChange={showYieldChange}
showAllDividends={showAllDividends}/>
)
}
}
// https://stackoverflow.com/questions/38619981/how-can-i-prevent-event-bubbling-in-nested-react-components-on-click
const renderRecentSearches = () => {
return recentSearches.map((term) => {
return (
<div key={term}>
<button
onClick={() => recentSearchOnClick(term)}
style={{marginRight: '10px'}}
>
<div>{term} </div>
</button>
<button
onClick={(event) => {event.stopPropagation(); removeRecentSearchOnClick(term)}}>
X
</button>
<br/><br/>
</div>
)
})
}
const renderSettingsView = (data) => {
if (settingsViewVisible) {
return (
<SettingsView data={data} />
)
} else {
return null;
}
}
const toggleSettingsView = () => {
setSettingsViewVisible(!settingsViewVisible);
}
const toggleDisplay = (e, setter) => {
setter(e.target.checked)
}
const SETTINGS_DATA = [
{
label: 'Main info',
id: 'main_info',
toggler: toggleDisplay,
setter: setShowMainInfo
},
{
label: 'Yield change',
id: 'yield_change',
toggler: toggleDisplay,
setter: setShowYieldChange
},
{
label: 'Dividends list',
id: 'all_dividends',
toggler: toggleDisplay,
setter: setShowAllDividends
},
]
console.log("showMainInfo: ", showMainInfo);
console.log("showYieldChange: ", showYieldChange);
console.log("showAllDividends: ", showAllDividends);
return (
<div className="ui container" style={{marginTop: '10px'}}>
<SearchBar term={term} onTermUpdate={onTermUpdate} />
{renderRecentSearches()}
<br/><br/>
<button onClick={toggleSettingsView}>Display settings</button>
{renderSettingsView(SETTINGS_DATA)}
<div className="ui segment">
{renderMainContent()}
</div>
</div>
)
}
const mapStateToProps = state => {
return { userId: state.auth.userId };
};
export default connect(
mapStateToProps
)(SearchPage);
// export default SearchPage;
the settingsView component:
import React from 'react';
import SettingsCheckbox from './SettingsCheckbox';
const SettingsView = ({data}) => {
const checkboxes = data.map((checkbox_info) => {
return (
<div key={checkbox_info.id}>
<SettingsCheckbox
id={checkbox_info.id}
label={checkbox_info.label}
toggler={checkbox_info.toggler}
setter={checkbox_info.setter}/>
<br/>
</div>
)
});
return (
<div className="ui segment">
{checkboxes}
</div>
);
}
export default SettingsView;
SettingsCheckbox.js:
import React, {useState} from 'react';
const SettingsCheckbox = ({id, label, toggler, setter}) => {
const [checked, setChecked] = useState(true)
return (
<div style={{width: '200px'}}>
<input
type="checkbox"
checked={checked}
id={id}
name={id}
value={id}
onChange={(e) => {
setChecked(!checked);
toggler(e, setter);
}} />
<label htmlFor="main_info">{label}</label><br/>
</div>
);
}
export default SettingsCheckbox;

Edit Note in react redux

Im trying to make editing note functionality in my notes app and stuck on this point. Can somebody help me with it? In this version of code I have error 'dispatch is not a function' in EDIT_NOTE reducer.
So, here is my:
Actions:
import { ADD_NOTE, DELETE_NOTE, EDIT_NOTE } from './actionTypes'
export const editNoteAction = (text, id) => ({
type: EDIT_NOTE,
payload: {
id,
text,
},
})
Reducer:
import { nanoid } from 'nanoid'
import { ADD_NOTE, DELETE_NOTE, EDIT_NOTE } from './actionTypes'
const date = new Date()
export const initialState = {
notes: [
{
id: nanoid(),
text: '',
date: '',
},
],
}
export default function notes(state = initialState, { type, payload }) {
console.log(type)
switch (type) {
case ADD_NOTE: {
...
case DELETE_NOTE: {
...
case EDIT_NOTE: {
const editedNote = {
text: payload.text,
id: payload.id,
date: date.toLocaleDateString(),
}
return {
notes: state.notes.map((note) =>
note.id === payload.id
? {
...state,
notes: [...state.notes, editedNote],
}
: state
),
}
}
default:
return state
}
}
NoteList - the component where all notes are collected:
const NoteList = ({
Notes,
addNoteAction,
deleteNoteAction,
editNoteAction,
}) => {
console.log(Notes)
return (
<div className={styles.notesList}>
<AddNote handleAddNote={addNoteAction} />
{Notes.map((note) => (
<Note
key={note.id}
id={note.id}
text={note.text}
date={note.date}
handleDeleteNote={deleteNoteAction}
handleEditNote={editNoteAction}
/>
))}
</div>
)
}
const mapStateToProps = (state) => ({
Notes: state.notes || [],
})
const mapDispatchToProps = (dispatch) => ({
addNoteAction: (text) => dispatch(addNoteAction(text)),
deleteNoteAction: (id) => dispatch(deleteNoteAction(id)),
editNoteAction: (id) => dispatch(editNoteAction(id)),
})
export default connect(mapStateToProps, mapDispatchToProps)(NoteList)
And Note component
const Note = ({
id,
text,
date,
handleDeleteNote,
handleEditNote,
editNoteAction,
Notes,
}) => {
const [animType, setAnimType] = useState(AnimationTypes.ANIM_TYPE_ADD)
const [isEdit, setIsEdit] = useState(false)
const handleToggleEdit = () => {
if (!isEdit) {
editNoteAction(text)
}
setIsEdit(!isEdit)
}
const [newText, setNewText] = useState('')
const handleTextChange = (e) => {
setNewText(e.target.value)
}
const onNoteSave = () => {
handleToggleEdit()
editNoteAction({
id,
date,
text: newText,
})
}
return (
<div className={classnames(styles.note, styles[animType])}>
{isEdit ? (
<IsEditingNote
formalClassName={styles.editNote}
formalRef={noteTextareaRef}
formalOnChange={handleTextChange}
formalValue={newText}
/>
) : (
<span className={styles.noteText}>{text}</span>
)}
<div className={styles.noteFooter}>
<small>{date}</small>
<div className={styles.footerIcons}>
{!isEdit ? (
<EditingIcon
formalClassName={styles.editIcon}
formalOnClick={handleToggleEdit}
/>
) : (
<MdCheck
className={styles.deleteIcon}
size="1.4em"
onClick={onNoteSave}
/>
)}
<MdDeleteForever
className={styles.deleteIcon}
size="1.2em"
onClick={() => {
setAnimType(AnimationTypes.ANIM_TYPE_DELETE)
setTimeout(() => handleDeleteNote(id), 500)
}}
/>
</div>
</div>
</div>
)
}
const mapStateToProps = (state) => ({
Notes: state.notes || [],
})
const mapDispatchToProps = (dispatch) => ({
editNoteAction: (editedNote) => dispatch(editNoteAction(editedNote)),
})
export default connect(mapDispatchToProps, mapStateToProps)(Note)

Get an Error that .filter is not a function

I have tried to create an autocomplete suggestion box from an Thailand's province database URL.
This is my source code. I export this to App.js in src directory
import React, { useEffect, useState, useRef } from "react";
const Test = () => {
const [display, setDisplay] = useState(false);
const [singleProvince, setSingleProvince] = useState([]);
const [singleProvinceData, setSingleProvinceData] = useState([]);
const [search, setSearch] = useState("");
const wrapperRef = useRef(null);
const province_dataBase_url = 'https://raw.githubusercontent.com/earthchie/jquery.Thailand.js/master/jquery.Thailand.js/database/raw_database/raw_database.json'
useEffect(() => {
const promises = new Array(20).fill(fetch(province_dataBase_url)
.then((res) => {
return res.json().then((data) => {
const createSingleProvince = data.filter( (each) => {
if (false == (singleProvince.includes(each.province))) {
setSingleProvince(singleProvince.push(each.province))
setSingleProvinceData(singleProvinceData.push(each))
}
})
return data;
}).catch((err) => {
console.log(err);
})
}))
}, [])
useEffect(() => {
window.addEventListener("mousedown", handleClickOutside);
return () => {
window.removeEventListener("mousedown", handleClickOutside);
};
});
const handleClickOutside = event => {
const { current: wrap } = wrapperRef;
if (wrap && !wrap.contains(event.target)) {
setDisplay(false);
}
};
const updateProvince = inputProvince => {
setSearch(inputProvince);
setDisplay(false);
};
return (
<div ref={wrapperRef} className="flex-container flex-column pos-rel">
<input
id="auto"
onClick={() => setDisplay(!display)}
placeholder="Type to search"
value={search}
onChange={event => setSearch(event.target.value)}
/>
{display && (
<div className="autoContainer">
{ singleProvinceData
.filter( ({province}) => province.indexOf(search.toLowerCase()) > -1)
.map( (each,i) => {
return (
<div
onClick={() => updateProvince(each.province)}
className="singleProvinceData"
key={i}
tabIndex="0"
>
<span>{each.province}</span>
</div>
)
})}
</div>
)}
</div>
);
}
export default Test
When click on an input box, the console says "TypeError: singleProvinceData.filter is not a function"
enter image description here
I cannot find out what's wrong with my code
The issue is with the "singleProvinceData" state is not set correctly.
you cannot push data directly into the state.
useEffect(() => {
const promises = new Array(20).fill(fetch(province_dataBase_url)
.then((res) => {
return res.json().then((data) => {
const shallowSingleProvinceList = [];
const shallowSingleProvinceDataList = [];
const createSingleProvince = data.filter( (each) => {
if (false == (singleProvince.includes(each.province))) {
shallowSingleProvinceList.push(each.province)
shallowSingleProvinceDataList.push(each)
}
})
setSingleProvince(shallowSingleProvinceList)
setSingleProvinceData(shallowSingleProvinceDataList)
return data;
}).catch((err) => {
console.log(err);
})
}))
}, [])
You can show the data conditionally
{display && (
<div className="autoContainer">
{ singleProvinceData && singleProvinceData
.filter( ({province}) => province.indexOf(search.toLowerCase()) > -1)
.map( (each,i) => {
return (
<div
onClick={() => updateProvince(each.province)}
className="singleProvinceData"
key={i}
tabIndex="0"
>
<span>{each.province}</span>
</div>
)
})}
</div>
)}

Why isn't this button showing when the state is false?

I have created a component to function as a "Like/Unlike" button. When the state is true, the "Unlike" button successfully displays, but when I click "Unlike", and it DOES unlike successfully, the state should be set to false as (liked: false). However, I don't see the button.
One thing I noticed is, when I click "Unlike", the "Unlike" button disappears and the "Like" button does appear, for a millisecond, and then it vanishes in thin air. I cannot figure it out why.
Here are all the codes for my like button component:
import React from "react";
import { API, graphqlOperation } from "aws-amplify";
import { Button } from "element-react";
import { createLike, deleteLike } from "../graphql/mutations";
import { UserContext } from "../App";
class Like extends React.Component {
state = {
liked: "",
};
componentDidMount() {
this.setLiked();
}
setLiked() {
console.log(this.props);
const { user } = this.props;
const { post } = this.props;
if (post.likes.items.find((items) => items.liker === user.username)) {
this.setState({ liked: true });
console.log("liked: true");
} else {
this.setState({ liked: false });
console.log("liked: false");
}
}
handleLike = async (user) => {
try {
const input = {
liker: user.username,
likePostId: this.props.postId,
};
await API.graphql(graphqlOperation(createLike, { input }));
this.setState({
liked: true,
});
console.log("Liked!");
} catch (err) {
console.log("Failed to like", err);
}
};
handleUnlike = async (likeId) => {
try {
const input = {
id: likeId,
};
await API.graphql(graphqlOperation(deleteLike, { input }));
this.setState({
liked: false,
});
console.log("Unliked!");
} catch (err) {
console.log("Failed to unlike", err);
}
};
render() {
const { like } = this.props;
const { liked } = this.state;
return (
<UserContext.Consumer>
{({ user }) => (
<React.Fragment>
{liked ? (
<Button type="primary" onClick={() => this.handleUnlike(like.id)}>
Unlike
</Button>
) : (
<Button
type="primary"
onClick={() => this.handleLike(user, like.id)}
>
Like
</Button>
)}
</React.Fragment>
)}
</UserContext.Consumer>
);
}
}
export default Like;
The code of the parent component:
import React from "react";
import { API, graphqlOperation } from "aws-amplify";
import {
onCreateComment,
onCreateLike,
onDeleteLike,
} from "../graphql/subscriptions";
import { getPost } from "../graphql/queries";
import Comment from "../components/Comment";
import Like from "../components/Like";
import LikeButton from "../components/LikeButton";
import { Loading, Tabs, Icon } from "element-react";
import { Link } from "react-router-dom";
import { S3Image } from "aws-amplify-react";
import NewComment from "../components/NewComment";
class PostDetailPage extends React.Component {
state = {
post: null,
isLoading: true,
isAuthor: false,
};
componentDidMount() {
this.handleGetPost();
this.createCommentListener = API.graphql(
graphqlOperation(onCreateComment)
).subscribe({
next: (commentData) => {
const createdComment = commentData.value.data.onCreateComment;
const prevComments = this.state.post.comments.items.filter(
(item) => item.id !== createdComment.id
);
const updatedComments = [createdComment, ...prevComments];
const post = { ...this.state.post };
post.comments.items = updatedComments;
this.setState({ post });
},
});
this.createLikeListener = API.graphql(
graphqlOperation(onCreateLike)
).subscribe({
next: (likeData) => {
const createdLike = likeData.value.data.onCreateLike;
const prevLikes = this.state.post.likes.items.filter(
(item) => item.id !== createdLike.id
);
const updatedLikes = [createdLike, ...prevLikes];
const post = { ...this.state.post };
post.likes.items = updatedLikes;
this.setState({ post });
},
});
this.deleteLikeListener = API.graphql(
graphqlOperation(onDeleteLike)
).subscribe({
next: (likeData) => {
const deletedLike = likeData.value.data.onDeleteLike;
const updatedLikes = this.state.post.likes.items.filter(
(item) => item.id !== deletedLike.id
);
const post = { ...this.state.post };
post.likes.items = updatedLikes;
this.setState({ post });
},
});
}
componentWillUnmount() {
this.createCommentListener.unsubscribe();
}
handleGetPost = async () => {
const input = {
id: this.props.postId,
};
const result = await API.graphql(graphqlOperation(getPost, input));
console.log({ result });
this.setState({ post: result.data.getPost, isLoading: false }, () => {});
};
checkPostAuthor = () => {
const { user } = this.props;
const { post } = this.state;
if (user) {
this.setState({ isAuthor: user.username === post.author });
}
};
render() {
const { post, isLoading } = this.state;
return isLoading ? (
<Loading fullscreen={true} />
) : (
<React.Fragment>
{/*Back Button */}
<Link className="link" to="/">
Back to Home Page
</Link>
{/*Post MetaData*/}
<span className="items-center pt-2">
<h2 className="mb-mr">{post.title}</h2>
</span>
<span className="items-center pt-2">{post.content}</span>
<S3Image imgKey={post.file.key} />
<div className="items-center pt-2">
<span style={{ color: "var(--lightSquidInk)", paddingBottom: "1em" }}>
<Icon name="date" className="icon" />
{post.createdAt}
</span>
</div>
<div className="items-center pt-2">
{post.likes.items.map((like) => (
<Like
user={this.props.user}
like={like}
post={post}
postId={this.props.postId}
/>
))}
</div>
<div className="items-center pt-2">
{post.likes.items.length}people liked this.
</div>
<div>
Add Comment
<NewComment postId={this.props.postId} />
</div>
{/* Comments */}
Comments: ({post.comments.items.length})
<div className="comment-list">
{post.comments.items.map((comment) => (
<Comment comment={comment} />
))}
</div>
</React.Fragment>
);
}
}
export default PostDetailPage;
I think I know why it doesn't show up. It's because at first when the user hasn't liked it, there is no "like" object, so there is nothing to be shown, as it is only shown when there is a "like" mapped to it. I don't know how to fix it though.

Categories

Resources