I am trying to pass a function from a parent to child component to have the child change the parents state. Basically I have a search bar that needs to change what is displayed on the main page.
When I check the type of the function in the parent it shows up as a function but when I send and check it in the child its type is undefined. I get an error that its not a function whenever I try to call it in the child component
import LineChart from "./charts/LineChart";
import React, { Component } from 'react';
import Player from "../Components/Player";
import SearchBar from '../Components/SearchBar';
class Future extends Component {
state = {
players: [],
data: [],
playerID : ""
};
async componentDidMount() {
if (Player.playerID == "") {
const response = await fetch('http://localhost:8080/random');
const body = await response.json();
this.setState({ players: body });
console.log(body[0].player_id);
this.setState({ playerID: body[0].player_id })
this.setData(body[0].player_id)
} else {
this.displayPlayer(Player.playerID)
this.setData(Player.playerID)
}
}
async displayPlayer(playerID) {
if (playerID != "") {
const response = await fetch('http://localhost:8080/get_player/' + playerID);
const body = await response.json();
this.setState({ players: body });
}
}
onSearchChange = (value) => {
this.setState({ playerID: value });
}
async setData(id) {
const response = await fetch('http://localhost:8080/goal_prediction/' + id);
const body = await response.json();
this.setState({ data: body });
console.log(body);
}
render() {
this.displayPlayer(Player.playerID)
const { players, data, id } = this.state;
return (
<div>
<SearchBar placeholder={"Search"} stateChange={this.onSearchChange} />
{players.map(player =>
<div key={player.id}>
{player.name}'s goals
</div>
)}
<LineChart />
Goals predicted for next season: {data.predicted_goals }
</div>
);
}
}
export default Future;
import './SearchBar.css';
import React, { useState } from 'react';
import CloseIcon from '#mui/icons-material/Close';
import SearchIcon from '#mui/icons-material/Search';
import Player from '../Components/Player';
import Future from '../pages/FutureStats';
function SearchBar({ placeholder }, { stateChange }) {
const [filteredData, setFilteredData] = useState([]);
const [wordEntered, setWordEntered] = useState("");
const handleFilter = async (event) => {
const searchWord = event.target.value // Access the value inside input
setWordEntered(searchWord);
const url = 'http://localhost:8080/search_player/' + searchWord;
const response = await fetch(url);
const body = await response.json();
if (searchWord === "") {
setFilteredData([])
} else {
setFilteredData(body);
}
}
const clearInput = () => {
setFilteredData([]);
setWordEntered("");
}
const selectInput = value => () => {
console.log(Player.playerID)
Player.playerID
setFilteredData([]);
setWordEntered("");
console.log(typeof(stateChange))
stateChange(value);
}
return (
<div className='search'>
<div className='searchInputs'>
<input type={"text"} value={wordEntered} placeholder={placeholder} onChange={handleFilter} />
<div className='searchIcon'>
{filteredData.length === 0 ? <SearchIcon/> : <CloseIcon id="clearButton" onClick={clearInput} />}
</div>
</div>
{filteredData.length !== 0 && (
<div className='dataResult'>
{filteredData.slice(0, 15).map((value) => {
return (
// Stay on one page.
<a className="dataItem" target="_blank" rel="noopener noreferrer" onClick={selectInput(value.player_id)}>
<p key={value.id}>{value.name}</p>
</a>
);
})}
</div>
)}
</div>
);
}
export default SearchBar;
stateChange is part of your props and needs to be the first argument in your SearchBar function:
function SearchBar({ placeholder, stateChange }) {
...
Related
I am developing an app with react and redux toolkit. I am using an API. the part that I would like help with is this:
I have a favorite Icon. this icon takes a copy of the movie and displays it in the favorite section. There is another icon which is for watched movies. so the idea is that if I click on watched it should disable only the favorite icon of the movie card I clicked. however, it disables all favorite icons for all movie cards. The watched icon is referred to as Eye the favorite icon is referred to as star
This is the eye component (Watched)
import { useState } from "react";
import { BsEyeSlash, BsEyeFill } from "react-icons/bs";
import { useDispatch, useSelector } from "react-redux";
import {
add_watched,
remove_watched,
add_occupied,
remove_occupied,
} from "../features/animeSlice";
const Eye = ({ anime, type }) => {
const [active_eye, setActive_eye] = useState(false);
const dispatch = useDispatch();
const toggle_eye = () => {
if (!active_eye) {
setActive_eye((prev) => {
return !prev;
});
dispatch(add_watched(anime));
dispatch(add_occupied(type));
} else {
setActive_eye((prev) => {
return !prev;
});
dispatch(remove_watched(anime?.mal_id));
dispatch(remove_occupied());
}
};
return (
<div>
{!active_eye ? (
<BsEyeSlash
className="text-xl text-green-500 icons_1"
onClick={toggle_eye}
/>
) : (
<BsEyeFill
className={"text-xl text-red-500 icons_1"}
onClick={toggle_eye}
/>
)}
</div>
);
};
export default Eye;
This is the Star Component (Favorite)
import { useState } from "react";
import { AiOutlineStar, AiFillStar } from "react-icons/ai";
import { add_favourite, remove_favourite } from "../features/animeSlice";
import { useDispatch, useSelector } from "react-redux";
const Star = ({ anime }) => {
const { value} = useSelector((state) => state.anime);
const [active_star, setActive_star] = useState(false);
const dispatch = useDispatch();
const toggle_star = () => {
if (!active_star) {
setActive_star((prev) => {
return !prev;
});
dispatch(add_favourite(anime));
} else {
setActive_star((prev) => {
return !prev;
});
dispatch(remove_favourite(anime?.mal_id));
}
};
return (
<div>
{!active_star ? (
<AiOutlineStar
className={
value === "occupied"
? "text-xl text-gray-300 pointer-events-none"
: "text-xl text-yellow-500 icon_1"
}
onClick={toggle_star}
/>
) : (
<AiFillStar
className={
value === "occupied"
? "text-xl text-gray-300 pointer-events-none"
: "text-xl text-yellow-500 icon_1"
}
onClick={toggle_star}
/>
)}
</div>
);
};
export default Star;
this is the redux toolkit slice
import { createSlice } from "#reduxjs/toolkit";
const initialState = {
favourite: [],
watched: [],
value: "",
page:""
};
const animeSlice = createSlice({
name: "anime",
initialState,
reducers: {
add_favourite(state, { payload }) {
state.favourite.push(payload);
},
remove_favourite(state, { payload }) {
state.favourite = state.favourite.filter(
(anime) => anime?.mal_id !== payload
);
},
add_watched(state, { payload }) {
state.watched.push(payload);
},
remove_watched(state, { payload }) {
state.watched = state.watched.filter((anime) => anime?.mal_id !== payload);
},
add_occupied(state, { payload }) {
state.value = payload;
},
remove_occupied(state) {
state.value = "";
},
pageNumber(state, { payload }) {
state.page = payload
}
},
});
export const {
add_favourite,
remove_favourite,
add_watched,
remove_watched,
add_occupied,
remove_occupied,
pageNumber
} = animeSlice.actions;
export default animeSlice.reducer;
This is the component that holds the component of eye and star
import { useState, useEffect } from "react";
import { Link } from "react-router-dom";
import Eye from "./Eye";
import Star from "./Star";
const TopAnime = ({ anime }) => {
//pagination
//
//
let colorYear = (year) => {
if (year === "N/A") {
return "text-violet-500";
} else {
return "";
}
};
return (
<section className="grand px-4 pt-2 pb-5 w-64 bg-white/5 bg-opacity-80 backdrop-blur-sm rounded-lg cursor-pointer font-poppins animate-slideup">
<div className="pb-1 wrapper_icons">
<div className="wrapper_hover">
<Eye anime={anime} type="occupied" />
<Star anime={anime} />
</div>
</div>
<Link to={`/anime/${anime?.mal_id}`}>
<div className="wrapper_1 flex flex-col items-center justify-center">
<div className="h-[313px] w-[219px]">
<img
src={anime?.images?.jpg?.large_image_url}
alt={anime?.title}
className="h-full w-full"
/>
</div>
</div>
<div className="flex flex-col mt-3">
<p className="text-sm text-white truncate mx-1">
{anime?.title_english ? anime?.title_english : anime?.title}
</p>
<div className="flex justify-between items-center text-sm text-yellow-500 mx-1">
<p className={colorYear(anime?.year ? anime?.year : "N/A")}>
{anime?.year ? anime?.year : "N/A"}
</p>
<p
className={
anime?.score <= 7
? "text-cyan-500"
: anime?.score <= 5
? "text-red-600"
: "text-green-500"
}
>
{anime?.score}
</p>
</div>
</div>
</Link>
</section>
);
};
export default TopAnime;
This is where TopAnime is rendered
import { useGetAnimeQuery } from "../features/API";
import { useDispatch, useSelector } from "react-redux";
import { useState, useEffect } from "react";
import TopAnime from "../components/TopAnime";
import Spinner from "../components/Spinner";
import { NavLink, Link } from "react-router-dom";
import Paginator from "../components/Paginator";
//import ReactPaginate from "react-paginate";
import Pagination from "#mui/material/Pagination";
const Home = () => {
const [page, setPage] = useState(1);
const {
data: manga = [],
isLoading,
isFetching,
error,
} = useGetAnimeQuery(page);
const { data, pagination } = manga;
//destructuring objects
//const { data, pagination } = manga;
//const top_anime = data;
const total = Math.ceil(pagination?.items?.total / 24)
//const current_page = pagination?.current_page;
//const per_page = pagination?.items?.per_page;
//const { items: pages } = total;
/* let fetchData = async (page = 1) => {
let res = await fetch(
`https://api.jikan.moe/v4/top/anime?page=${page}&limit=24`
);
let query = await res.json();
const { data, pagination } = query;
let totalPages = Math.ceil(pagination?.items.total / 24);
setPageCount(totalPages);
setData(data);
};
useEffect(() => {
fetchData();
}, []);
*/
import { useGetAnimeQuery } from "../features/API";
import { useDispatch, useSelector } from "react-redux";
import { useState, useEffect } from "react";
import TopAnime from "../components/TopAnime";
import Spinner from "../components/Spinner";
import { NavLink, Link } from "react-router-dom";
import Paginator from "../components/Paginator";
//import ReactPaginate from "react-paginate";
import Pagination from "#mui/material/Pagination";
const Home = () => {
const [page, setPage] = useState(1);
const {
data: manga = [],
isLoading,
isFetching,
error,
} = useGetAnimeQuery(page);
const { data, pagination } = manga;
//destructuring objects
//const { data, pagination } = manga;
//const top_anime = data;
const total = Math.ceil(pagination?.items?.total / 24)
//const current_page = pagination?.current_page;
//const per_page = pagination?.items?.per_page;
//const { items: pages } = total;
/* let fetchData = async (page = 1) => {
let res = await fetch(
`https://api.jikan.moe/v4/top/anime?page=${page}&limit=24`
);
let query = await res.json();
const { data, pagination } = query;
let totalPages = Math.ceil(pagination?.items.total / 24);
setPageCount(totalPages);
setData(data);
};
useEffect(() => {
fetchData();
}, []);
*/
const handleChange = (event, value) => {
setPage(value);
};
const display = data?.map((anime) => (
<TopAnime anime={anime} key={anime.mal_id} />
));
//const pageCount = Math.ceil(pagination?.items?.total / 24);
if (isLoading) {
return <Spinner color_1={"#141e30"} color_2={"#243b55"} />;
} else if (isFetching) {
return <Spinner color_1={"#141e30"} color_2={"#243b55"} />;
} else if (error) {
console.log("ERROR =>", error.message);
}
return (
<section className="bg-gradient-to-r from-[#141e30] to-[#243b55]">
<div className="container font-poppins">
<div className="grid grid-cols-4 gap-3 place-items-center px-20">
{/* {top_anime &&
top_anime?.map((anime) => (
<TopAnime anime={anime} key={anime.mal_id} />
))} */}
{display}
</div>
<div className="button text-yellow-500 flex items-center justify-center mt-2 pb-2 cursor-pointer">
{/* <Paginator paginated={paginated} NumP={pagination?.last_visible_page} /> */}
{/* <ReactPaginate
previousLabel={"Previous"}
nextLabel={"Next"}
onPageChange={(page) => fetchData(page.selected + 1)}
pageCount={pageCount}
className="flex space-x-2"
activeClassName="active"
/> */}
<Pagination count={total} page={page} onChange={handleChange} defaultPage={1} boundaryCount={3} color="secondary" sx={{button:{color:'#ffffff'}}} />
</div>
</div>
</section>
);
};
export default Home;
Issue
The issue with the code/current implementation is that there is only one state.anime.value and one "occupied" value. Each time a movie's "watched" status is toggled the Redux state sets/unsets the single state.anime.value state. In other words, you can toggle N movies "watched" and the state.anime.value value is "occupied", but then toggling just 1 movie back to "unwatched" and state.anime.value is reset back to "". All the Star components read this single state value and this is why the stars all toggle together.
Solution
If you want a specific movie to toggle the Star component when the Eye icon is toggled then I think the solution is a bit simpler than you are making it out to be in your code.
The "eye" and "star" components effectively duplicate your Redux state and don't synchronize well with it. It's often considered a React anti-pattern to duplicate state. The state.anime.favourite and state.anime.watched arrays are all the app needs to know what has been marked "watched" and what has been favourited.
Update the animeSlice to store the anime.mal_id properties in objects instead of arrays to provide O(1) lookup in the UI. Remove the anime.value state as it's unnecessary.
animeSlice.js
import { createSlice } from "#reduxjs/toolkit";
const initialState = {
favourite: {},
watched: {},
page: ""
};
const animeSlice = createSlice({
name: "anime",
initialState,
reducers: {
add_favourite(state, { payload }) {
state.favourite[payload] = true;
},
remove_favourite(state, { payload }) {
delete state.favourite[payload];
},
add_watched(state, { payload }) {
state.watched[payload] = true;
delete state.favourite[payload]; // un-star when adding as watched
},
remove_watched(state, { payload }) {
delete state.watched[payload];
},
pageNumber(state, { payload }) {
state.page = payload;
}
}
});
export const {
add_favourite,
remove_favourite,
add_watched,
remove_watched,
pageNumber
} = animeSlice.actions;
export default animeSlice.reducer;
Update the Eye and Star components to read the true state from the store. These components should pass anime.mal_id to the dispatched actions and use anime.mal_id to check the watched/favourites map objects.
Eye
import { BsEyeSlash, BsEyeFill } from "react-icons/bs";
import { useDispatch, useSelector } from "react-redux";
import { add_watched, remove_watched } from "../features/animeSlice";
const Eye = ({ anime }) => {
const dispatch = useDispatch();
const { watched } = useSelector((state) => state.anime);
const isWatched = watched[anime.mal_id];
const toggle_eye = () => {
dispatch((isWatched ? remove_watched : add_watched)(anime.mal_id));
};
const Eye = isWatched ? BsEyeFill : BsEyeSlash;
const className = [
"text-xl icons_1",
isWatched ? "text-green-500" : "text-red-500"
].join(" ");
return (
<div>
<Eye className={className} onClick={toggle_eye} />
</div>
);
};
export default Eye;
Star
import { AiOutlineStar, AiFillStar } from "react-icons/ai";
import { add_favourite, remove_favourite } from "../features/animeSlice";
import { useDispatch, useSelector } from "react-redux";
const Star = ({ anime }) => {
const { favourite, watched } = useSelector((state) => state.anime);
const dispatch = useDispatch();
const isFavorited = favourite[anime.mal_id];
const isWatched = watched[anime.mal_id];
const toggle_star = () => {
dispatch((isFavorited ? remove_favourite : add_favourite)(anime.mal_id));
};
const Star = isFavorited ? AiFillStar : AiOutlineStar;
const className = [
"text-xl",
isWatched ? "text-gray-300 pointer-events-none" : "text-yellow-500 icon_1"
].join(" ");
return (
<div>
<Star className={className} onClick={toggle_star} />
</div>
);
};
export default Star;
I currently have a <textarea/> field that when you press enter it submits and makes a new item on the array. When you press shift + enter it creates a new line in the <textarea/> input field.
However, when you actually press shift and enter to make a new break and submit it; it does not recognize the break in the line. I have attached images below.
As you can see above, its like the array does not recognize there is a break in the input.
Todobox.jsx:
import React, { createContext } from 'react';
import Item from './Item';
import { useState, useContext } from 'react';
import '../App.css';
import trash from '../trash_can.png'
import { ElementContext } from '../ElementContext';
export const ItemContext = createContext();
export const ItemContextProvider = ({ children }) => {
const [items, setItems] = useState([]);
const [itemId, setItemId] = useState(1);
const [itemData, setItemData] = useState();
const [refDict, setRefDict] = useState({});
const newItemId = (items) =>{
setItemId(itemId + 1);
console.log(itemId)
}
const newItem = (itemChange, boxid) => {
newItemId();
if (!refDict[itemId]) {
setItems(prev => [...prev, { itemboxid: boxid, itemdata: itemChange, itemid: itemId }]);
setRefDict((prev) => ({...prev, [itemId]: true}));
}
console.log(items);
};
const value = {
items,
setItems,
newItem,
itemId
};
return(
<ItemContext.Provider value={value}>
{children}
</ItemContext.Provider>
)
};
export default function Todobox({ boxtitle, boxid }){
const { elements, setElements, newTitle } = useContext(ElementContext);
const { items, setItems, newItem } = useContext(ItemContext);
const [boxheader, setBoxHeader] = useState('');
const [itemChange, setItemChange] = useState('');
const handleChange = (e) => {
setBoxHeader(e.target.value);
}
const handleKeydown = (e) => {
if(e.keyCode == 13 && e.shiftKey == false){
setElements(elements.map(element => {
if (element.boxid === boxid) {
setBoxHeader(e.target.value)
return { ...element, boxtitle: boxheader };
} else {
return element;
}
}))
e.preventDefault();
alert('Title has been set to: ' + boxheader);
}
}
const handleDelete = (e) => {
setElements(elements.filter(element => element.boxid !== boxid))
}
const handleItemChange = (e) =>{
setItemChange(e.target.value);
}
const handleNewItem = (e) =>{
if(e.keyCode == 13 && e.shiftKey == false){
newItem(itemChange, boxid)
e.preventDefault();
e.target.value = '';
}
}
return(
<>
<div className='element-box'>
<img src={trash} className='element-box-trash' onClick={handleDelete}></img>
<textarea className='element-title-input' placeholder='Add title...' onChange={handleChange} onKeyDown={handleKeydown} value={boxheader}></textarea>
{items.map(item => {
if(item.itemboxid === boxid){
return <Item key={item.itemid} itemid={item.itemid} itemdata={item.itemdata}/>;
} else if(item.itemboxid !== boxid){
return null;
}
})}
<textarea
className='element-input'
type='text'
placeholder={`Add item... ${boxid}`}
onChange={handleItemChange}
onKeyDown={handleNewItem}
onClick={() => {console.log(boxid)}}
/>
</div>
</>
)
}
Item.jsx:
import React, { useContext } from 'react';
import '../App.css';
import { ElementContext } from '../ElementContext';
import { ItemContext } from './Todobox';
export default function Item({ itemid, itemdata }){
const { setHideModal, modals, setModals } = useContext(ElementContext);
const handleNewModal = () => {
setHideModal(false)
setModals(prev => [...prev, { modalItemId: itemid, modalId: '1', modalData: itemdata }]);
console.log(modals);
};
return(
<div className='item-container' onClick={handleNewModal}>
<a className='item-text'>{itemdata}</a>
</div>
)
}
Item-input css:
.item-text {
padding: 2px;
opacity: 1;
word-wrap: break-word;
}
Any help would be appreciated, thank you in advanced! :)
#caTS answered my question in a comment.
I had to add white-space: pre-wrap to my CSS.
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;
I have a dynamically created list of 5 input elements. When I now click on a "plus" icon element (from IonicIcons) in React, I want the first of those input fields to be focused.
My input List:
if (actualState.showInputField === true) {
inputField = (
<div>
{
actualState.inputFields.map((val,index) => (
<ListElemErrorBoundary key={index}>
<InputElement key={index} elemValue = {val} name={"input" + index} onChangeListener={(event) => handleDoublettenIDs(event,index)} />
</ListElemErrorBoundary>
)
)
}
{actualState.errorMessage!= '' ? <Alert color="danger" >{actualState.errorMessage}</Alert> : null}
{actualState.successMessage !='' ? <Alert color="success" >{actualState.successMessage}</Alert> : null}
<br />
<p><button onClick = { () => {
handleInputs(props.anzeigeID);
vanishMessage();
}
}>absenden</button></p>
</div>
)
}
return inputField;
}
My Icon:
const checkIcon = () => {
let showIcon = null;
if (actualState.showInputField === false) {
showIcon = (
<IoIosAddCircleOutline ref={toggleSignRef} onClick = {toggleInput}
/>
)
} else {
showIcon = (
<IoIosRemoveCircleOutline onClick = {toggleInput}
/>
)
}
return showIcon;
}
I probably should place my ref on the list items, however, I guess that for every new list element, this ref gets "overwritten" because I have only one ref. Should I do something like a input key query, to find out which list key input element this is, and if it is the first input key index, I execute a focus on that input element?
And how then can I retrieve the first input element inside the method toggleInput() where I set the showInputField value? Is it somehow possible to ask for the props.key of that input element's reference?
This component is a functional component and I use useRef only...
My Component:
import React, {useState, useRef, useEffect} from "react";
import { IoIosAddCircleOutline } from 'react-icons/io';
import { IoIosRemoveCircleOutline } from 'react-icons/io';
import InputElement from './inputElementDublette';
import fetch from 'isomorphic-unfetch';
import getConfig from 'next/config';
import ListElemErrorBoundary from './ListElemErrorBoundary';
import { Button, Alert } from 'reactstrap';
let url_link;
let port = 7766;
const { serverRuntimeConfig, publicRuntimeConfig } = getConfig();
const apiUrl = publicRuntimeConfig.apiUrl; //|| publicRuntimeConfig.apiUrl;
const server = publicRuntimeConfig.SERVERNAME;
let doublettenListe_link = `http://${server}:${port}/doubletten/`;
//functional component with state, with useState
const DubletteComponent = props => {
const toggleSignRef = useRef();
const [actualState, changeState] = useState({
showInputField: false,
dublettenIDs: [],
errorMessage: '',
successMessage: '',
inputFields: ['','','','',''],
visible : false,
});
const toggleInput = () => {
changeState({...actualState, showInputField: !actualState.showInputField});
}
const vanishMessage = ()=>{
window.setTimeout(() => {
changeState({
...actualState,
errorMessage:'',
successMessage: '',
});
},7000);
}
const handleDoublettenIDs = (event,index) => {
let idnumber = event.target.value;
let newInputFields = [...actualState.inputFields];
newInputFields.splice(index,1, idnumber);
//console.log("new:",newInputFields);
if (isNaN(idnumber)) {
changeState({...actualState, errorMessage: 'ID is not a number'})
} if (idnumber > 2147483647) {
changeState({...actualState, errorMessage: 'Number can not be bigger than 2147483647!'})
}
else {
changeState({...actualState, inputFields: newInputFields, errorMessage: '' });
}
}
const handleInputs = (anzeigeID) => {
if (process.browser && apiUrl==='dev') {
doublettenListe_link = `http://localhost:${port}/doubletten/`;
}
if (actualState.errorMessage=='') {
let array = actualState.inputFields;
let filtered = array.filter(function(el) {
return el != '';
});
const requestOptions = {
method: 'POST',
headers: {'Accept': 'application/json', 'Content-Type':'application/json'},
body: JSON.stringify({
id: anzeigeID,
dublettenIDs: filtered
})
};
//console.log("inputfields:",filtered);
// Note: I'm using arrow functions inside the `.fetch()` method.
// This makes it so you don't have to bind component functions like `setState`
// to the component.
//console.log("link:", doublettenListe_link);
fetch(doublettenListe_link , requestOptions)
.then((response) => {
//console.log("Resp:", response);
let tempArray = ['','','','',''];
changeState({...actualState, inputFields: tempArray});
//console.log(actualState);
changeState({...actualState, dublettenIDs: []});
changeState({...actualState, successMessage: `Doubletten-IDs wurden erfolgreich eingetragen!`});
return response;
}).catch((error) => {
changeState({...actualState, errorMessage: `Error beim Eintrage der Dubletten. Bitte prüfen, ob der Server läuft. Error: ${error.statusText}`});
});
}
}
const checkIcon = () => {
let showIcon = null;
if (actualState.showInputField === false) {
showIcon = (
<IoIosAddCircleOutline onClick = {toggleInput}
/>
)
} else {
showIcon = (
<IoIosRemoveCircleOutline onClick = {toggleInput}
/>
)
}
return showIcon;
}
const checkPrerequisites = () => {
//let errorMessage = '';
let inputField = null;
// if (actualState.errorMessage != '') {
// errorMessage = (
// <Alert color="danger">{actualState.errorMessage}</Alert>
// )
// }
//Outsourcing check for variable and return JSX elements on conditionals
if (actualState.showInputField === true) {
inputField = (
<div>
{
actualState.inputFields.map((val,index) => (
<ListElemErrorBoundary key={index}>
<InputElement key={index} elemValue = {val} name={"input" + index} onChangeListener={(event) => handleDoublettenIDs(event,index)} />
</ListElemErrorBoundary>
)
)
}
{actualState.errorMessage!= '' ? <Alert color="danger" >{actualState.errorMessage}</Alert> : null}
{actualState.successMessage !='' ? <Alert color="success" >{actualState.successMessage}</Alert> : null}
<br />
<p><button onClick = { () => {
handleInputs(props.anzeigeID);
vanishMessage();
}
}>absenden</button></p>
</div>
)
}
return inputField;
}
return (
<div >
{checkIcon()} Dubletten eintragen
{checkPrerequisites()}
</div>
)
}
export default DubletteComponent
My InputElement Component:
const inputElement = (props) => (
<p>
<input
ref={props.ref}
value ={props.elemValue}
name={props.name}
type="number"
max="2147483647"
placeholder="Doubletten-ID"
onChange={props.onChangeListener}>
</input>
</p>
)
export default inputElement
The issue is you can not pass ref from your parent component to child component. In new version of react you can achieve this by using forwardRef api. Use it like below if you are in react #16 version.
import React from 'react'
const inputElement = React.forwardRef(props) => (
<p>
<input
ref={props.ref}
value ={props.elemValue}
name={props.name}
type="number"
max="2147483647"
placeholder="Doubletten-ID"
onChange={props.onChangeListener}>
</input>
</p>
)
//To focus textinput
this.inputref.focus();
Happy coding :)
it is my first React App
i want create simple typeahead(autocomplete)
i want when i click on searched list of item, this item will show in value of my Parent input
now my click doesnt work, working only search by name
it is my parent
`
import React, { Component } from 'react';
import logo from './logo.svg';
import './Search.css';
import Sugg from './Sugg';
class Search extends Component {
constructor(props) {
super(props);
this.onSearch = this.onSearch.bind(this);
this.handleClickedItem = this.handleClickedItem.bind(this);
this.onClick = this.onClick.bind(this);
this.state = {
companies: [],
searchedList: [],
value: ''
}
}
componentDidMount() {
this.fetchApi();
console.log(this.state.companies);
}
fetchApi = ()=> {
const url = 'https://autocomplete.clearbit.com/v1/companies/suggest?query={companyName}';
fetch(url)
.then( (response) => {
let myData = response.json()
return myData;
})
.then((value) => {
let companies = value.map((company, i) => {
this.setState({
companies: [...this.state.companies, company]
})
})
console.log(this.state.companies);
});
}
onSearch(arr){
// this.setState({companies: arr});
};
handleInputChange = () => {
console.log(this.search.value);
let searched = [];
this.state.companies.map((company, i) => {
console.log(company.name);
console.log(company.domain);
const tempName = company.name.toLowerCase();
const tempDomain = company.domain.toLowerCase();
if(tempName.includes(this.search.value.toLowerCase()) || tempDomain.includes(this.search.value.toLowerCase())) {
searched.push(company);
}
})
console.log(searched);
this.setState({
searchedList: searched
})
if(this.search.value == '') {
this.setState({
searchedList: []
})
}
}
handleClickedItem(data) {
console.log(data);
}
onClick = e => {
console.log(e.target.value)
this.setState({ value: e.target.value});
};
render() {
return (
<div className="Search">
<header className="Search-header">
<img src={logo} className="Search-logo" alt="logo" />
<h1 className="Search-title">Welcome to React</h1>
</header>
<form>
<input
placeholder="Search for..."
ref={input => this.search = input}
onChange={this.handleInputChange}
/>
<Sugg searchedList={this.state.searchedList} onClick={this.onClick.bind(this)} />
</form>
</div>
);
}
}
export default Search;
`
and here my child component
i dont know how call correctly click event
import React from 'react';
const Sugg = (props) => {
console.log(props);
const options = props.searchedList.map((company, i) => (
<div key={i} >
<p onClick={() => this.props.onClick(this.props)}>{company.name}</p>
</div>
))
console.log(options);
return <div >{options}</div>
}
export default Sugg;
please help me who knows how it works
thanks a lot
In the parent you could modify your code:
onClick = company => {
console.log('company', company);
this.setState({ value: company.name});
};
and you don't need to bind this because onClick is an arrow function
<Sugg searchedList={this.state.searchedList} onClick={this.onClick} />
and in the child component, you need to use props from the parameters, not from the this context:
<p onClick={() =>props.onClick(company)}>{company.name}</p>