I have a searchbar component and a Catalog component. The Catalog component contains different cards. Depending on what is typed in the input field of the searchbar component I want to render different cards.
For this to work I need to be able to import the value of the input field into the Catalog component where it is passed in a search function that handles all the rest of the work.
I am able to import the value into my Catalog component but unfortunaty I can't figure out how I can tell if the imported value has changed so I can search again?
I have found some ways to do this with classes but I would like to use hooks instead. I have experimented a bit with "useEffect" but that didn't work out.
Thank you for your help!
This is my code in the searchbar component:
import React, { useState } from 'react';
let input = "";
function Search() {
const [value, setValue] = useState(input);
function onSearch(e) {
setValue(e.target.value);
input = value;
}
return(
<form className="searchForm">
<input className="search" type="text" name="search" autoComplete="off" placeholder="zoeken" value={value} onChange={onSearch}/> </form>
);
}
export { Search, input };
And this is the code in my Catalog
import React, { useState, useEffect } from 'react';
import {input} from "./search";
// other imports
function Catalog(props){
//get cards code and fuse code
const [query, setQuery] = useState(input);
function inputHasChanged(){ //function that can tell if the imported input variable changed
setQuery(input); //update query and rerender cards
}
const results = fuse.search(query)
const searchedCards = query ? results.map(card => card.item) : cards;
//other code
return(
<div>
//render the SearchedCards
</div>
);
}
export {Catalog};
Solution:
code in search:
import React, { useState } from 'react';
const Search = ({ searching }) => {
const [value, setValue] = useState("");
function submit(e){
setValue(e.target.value);
searching(value);
}
return (
<form className="searchForm">
<input
className="search"
type="text" name="search"
autoComplete="off"
placeholder="zoeken"
value={value}
onChange={submit}
/>
</form>
);
};
export { Search };
Search is a child of banner:
import React, {useState, useEffect} from 'react';
import {Search} from './search';
import Header from './Header';
import Overzicht from './Overzicht';
const Banner = ({ search }) => {
const [value, setValue] = useState("");
useEffect(() => {
search(value);
},[search, value]);
return(
<div className="banner">
<Header />
<Search searching={value => setValue(value)} />
<Overzicht />
</div>
);
};
export default Banner;
Banner is a child of home which also contains Catalog:
import React, { useState } from "react";
import Banner from './banner';
import {Catalog} from './Catalog';
function Home(){
const [input, setInput] = useState("");
return(
<div>
<section id="banner">
<Banner search={input => setInput(input)}/>
</section>
<section id="overzicht">
<Catalog search={input} />
</section>
</div>
);
}
export default Home;
And now I can just call
props.search
In Catalog
You can use useEffect as mentioned below:
useEffect(() => {
// Write your logic here
},[input]); // it will run only when the input changes
Push the common state, the query, up to a common ancestor and pass it down as needed to child and descendant components. This way they can "watch" the changes by having new props passed to them.
Below is a simplified version of a structure that would work:
function Catalog({ query }) {
const [results, setResults] = useState(null);
useEffect(() => {
// If `fuse.search` is asynchronous then you might need to debounce
// these queries and/or cancel old queries. If a user types "foo",
// a query is kicked off, and then they finish typing "food", you
// want to cancel the query for "foo" because the results will no
// longer be relevant.
const results = fuse.search(query);
setResults(results);
}, [query])
return (
<div />
);
}
function Search({ query, setQuery }) {
return (
<input onChange={setQuery} value={query} />
)
}
function App() {
const [query, setQuery] = useState("");
return (
<>
<Search query={query} setQuery={setQuery} />
<Catalog query={query} />
</>
);
}
Related
I have created a counter app in React js using context api for global state management .
But the problem is when i am clicking increase and decrease button it is not updating global values .
I am new to react , please provide guidance what is going wrong here .
ContextFile :
import {createContext,useState} from 'react';
export const DataContext = createContext({
data:0,
increase : () => {},
decrease : () => {}
});
function DataContextProvider(props){
const [data,setData] = useState();
const increase = () => {
setData(data + 1);
}
const decrease = () => {
setData(data - 1);
}
return(
<DataContext.Provider value={{data,increase,decrease}}>
{props.children}
</DataContext.Provider>
);
};
export default DataContextProvider;
App.js :
import React,{useContext} from 'react';
import {DataContext} from './Context/dataContext';
import DataContextProvider from './Context/dataContext';
import IncreaseBtn from './Component/Increase';
import DecreaseBtn from './Component/Decrease';
const App = () => {
const {data} = useContext(DataContext);
return(
<>
<DataContextProvider>
{data}
<br/>
<br/>
<IncreaseBtn />
<br/>
<br/>
<DecreaseBtn />
</DataContextProvider>
</>
)
}
export default App;
Increase Button Component :
import React,{useContext} from 'react';
import {DataContext} from '../Context/dataContext';
const IncreaseBtn = () => {
const {increase} = useContext(DataContext);
return(
<>
<button onClick={increase}> Increase </button>
</>
)
}
export default IncreaseBtn;
Decrease Button Component :
import React,{useContext} from 'react';
import {DataContext} from '../Context/dataContext';
const DecreaseBtn = () => {
const {decrease} = useContext(DataContext);
return(
<>
<button onClick={decrease}> Decrease </button>
</>
)
}
export default DecreaseBtn;
Folder Structure :
If you want to use context you should wrap your provider around those components, but here App component isn't wrapped but to its children 😉
Give an initial state of some "number" as it would be undefined and it gives NaN if you do the arithmetic operations with it.
Updated the sandbox for your ref
You are updating the state in the wrong way
Try:
setCount(count => count + 1);
I'm currently making a simple web frontend with react using react-autosuggest to search a specified user from a list. I want to try and use the Autosuggest to give suggestion when the user's type in the query in the search field; the suggestion will be based on username of github profiles taken from github user API.
What I want to do is to separate the AutoSuggest.jsx and then import it into Main.jsx then render the Main.jsx in App.js, however it keeps giving me 'TypeError: _ref2 is undefined' and always refer to my onChange function of AutoSuggest.jsx as the problem.
Below is my App.js code:
import './App.css';
import 'bootstrap/dist/css/bootstrap.min.css';
import Header from './views/header/Header';
import Main from './views/main/Main';
import Footer from './views/footer/Footer';
const App = () => {
return (
<>
<Header/>
<Main/> <- the autosuggest is imported in here
<Footer/>
</>
);
}
export default App;
Below is my Main.jsx code:
import React, { useState } from 'react';
import Container from 'react-bootstrap/Container';
import Row from 'react-bootstrap/Row';
import axios from 'axios';
import { useEffect } from 'react';
import AutoSuggest from '../../components/AutoSuggest';
const Main = () => {
const [userList, setUserList] = useState([]);
useEffect(() => {
axios.get('https://api.github.com/users?per_page=100')
.then((res) => setUserList(res.data))
.catch((err) => console.log(err));
}, [])
return (
<Container>
<br/>
<Row>
<AutoSuggest userList={userList} placeHolderText={'wow'} />
</Row>
</Container>
);
}
export default Main;
Below is my AutoSuggest.jsx code:
import React, { useState } from "react";
import Autosuggest from 'react-autosuggest';
function escapeRegexCharacters(str) {
return str.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
}
function getSuggestions(value, userList) {
const escapedValue = escapeRegexCharacters(value.trim());
if (escapedValue === '') {
return [];
}
const regex = new RegExp('^' + escapedValue, 'i');
return userList.filter(user => regex.test(user.login));
}
function getSuggestionValue(suggestion) {
return suggestion.name;
}
function renderSuggestion(suggestion) {
return (
<span>{suggestion.name}</span>
);
}
const AutoSuggest = ({userList, placeHolderText}) => {
const [value, setValue] = useState('');
const [suggestions, setSuggestions] = useState([]);
const onChange = (event, { newValue, method }) => { <- error from console always refer here, I'm not quite sure how to handle it..
setValue(newValue);
};
const onSuggestionsFetchRequested = ({ value }) => {
setValue(getSuggestions(value, userList))
};
const onSuggestionsClearRequested = () => {
setSuggestions([]);
};
const inputProps = {
placeholder: placeHolderText,
value,
onChange: () => onChange()
};
return (
<Autosuggest
suggestions={suggestions}
onSuggestionsFetchRequested={() => onSuggestionsFetchRequested()}
onSuggestionsClearRequested={() => onSuggestionsClearRequested()}
getSuggestionValue={() => getSuggestionValue()}
renderSuggestion={() => renderSuggestion()}
inputProps={inputProps} />
);
}
export default AutoSuggest;
The error on browser (Firefox) console:
I have no idea what does the error mean or how it happened and therefore unable to do any workaround.. I also want to ask if what I do here is already considered a good practice or not and maybe some inputs on what I can improve as well to make my code cleaner and web faster. Any input is highly appreciated, thank you in advance!
you have to write it like this... do not use the arrow function in inputProps
onChange: onChange
I am facing a problem with re-rendering after a state change in my NextJS app.
The function sendMessageForm launches a redux action sendMessage which adds the message to the state.
The problem is unrelated to the returned state in the reducer as I am returning a new object(return {...state}) which should trigger the re-render!
Is there anything that might block the re-render ?
This is the file that calls & displays the state, so no other file should be responsible ! But if you believe the problem might lie somewhere else, please do mention !
import { AttachFile, InsertEmoticon, Mic, MoreVert } from '#mui/icons-material';
import { Avatar, CircularProgress, IconButton } from '#mui/material';
import InfiniteScroll from 'react-infinite-scroller';
import Head from 'next/head';
import { useState, useEffect } from 'react';
import Message from '../../components/Message.component';
import styles from '../../styles/Chat.module.css'
import { useRouter } from 'next/router'
import {useSelector, useDispatch} from "react-redux"
import {bindActionCreators} from "redux"
import * as chatActions from "../../state/action-creators/chatActions"
const Chat = () => {
const router = useRouter()
const { roomId } = router.query
const auth = useSelector((state)=> state.auth)
const messages = useSelector((state)=> state.chat[roomId].messages)
const dispatch = useDispatch()
const {getMessages, markAsRead, sendMessage} = bindActionCreators(chatActions, dispatch)
const [inputValue, setInputValue] = useState("")
const sendMessageForm = (e) => {
e.preventDefault()
console.log("***inputValue:", inputValue)
sendMessage(roomId, inputValue)
}
const loadMessages = (page) => {
if(roomId)
getMessages(roomId, page)
}
//user-read-message
useEffect(() => {
//user-read-message
markAsRead(roomId, auth.user._id)
}, [messages]);
return (
<div className={styles.container}>
<Head>
<title>Chat</title>
</Head>
<div className={styles.header}>
<Avatar/>
<div className={styles.headerInformation}>
<h3>Zabre el Ayr</h3>
<p>Last Seen ...</p>
</div>
<div className={styles.headerIcons}>
<IconButton>
<AttachFile/>
</IconButton>
<IconButton>
<MoreVert/>
</IconButton>
</div>
</div>
<div className={styles.chatContainer}>
<InfiniteScroll
isReverse={true}
pageStart={0}
loadMore={loadMessages}
hasMore={messages.hasNextPage || false}
loader={<div className={styles.loader} key={0}><CircularProgress /></div>}
>
{Object.keys(messages.docs).map((key, index)=>{
return<Message
key={index}
sentByMe={messages.docs[key].createdBy === auth.user._id}
message={messages.docs[key].msg}
/>})}
</InfiniteScroll>
<span className={styles.chatContainerEnd}></span>
</div>
<form className={styles.inputContainer}>
<InsertEmoticon/>
<input className={styles.chatInput} value={inputValue} onChange={(e)=>setInputValue(e.target.value)}/>
<button hidden disabled={!inputValue} type='submit' onClick={sendMessageForm}></button>
<Mic/>
</form>
</div>)
};
export default Chat;
useSelector requires a new object with a new reference from the object you are passing to it in order to trigger the re-render
What you're doing with return {...state} is just creating a new object for the parent object but not the nested one useSelector is using, which is in your case :
const messages = useSelector((state)=> state.chat[roomId].messages)
So, you should return the whole state as a new object WITH a new state.chat[roomId].messages object
In other words, the references for the root object & the one being used should be changed.
I'm trying to fetch data in react. The problem is i have to click on button twice to get that data.
Although i don't get data on first click it somehow renders if I add JSON.stringify to it. If I don't add JSON.stringify it returns undefined. If anyone know what this is please help me
without clicking
on first click
on second click
import React, {useState,useEffect} from 'react';
import ReactDOM from 'react-dom';
import axios from 'axios'
function Example() {
const [students,setStudents] = useState('')
const [name,setName] = useState('')
const handleClick = async() => {
const data = await axios.get('api/foo')
setStudents(data)
console.log(students)
}
return (
<div className="container">
<h2>Example component</h2>
<button onClick = {handleClick}>Get students</button>
<div>
{JSON.stringify(students.data)}
</div>
</div>
);
}
export default Example;
if (document.getElementById('root')) {
ReactDOM.render(<Example />, document.getElementById('root'));
}
The problem was that setStudents is an asynchronous function, so I just made student object and added to it loading property
const [students,setStudents] = useState({
data: '',
loading: true
})
const [name,setName] = useState('')
const handleClick = async() => {
const data = await axios.get('api/foo')
setStudents({
data: data,
loading: false
})
}
return (
<div className="container">
<h2>Example component</h2>
<button onClick = {handleClick}>Get students</button>
<div>
{students.loading?'':
students.data.data[0].name}
</div>
</div>
);
}
setStudent is an asynchronous function. This means the value of students won't change immediately after you call setStudents.
Try shifting the console.log outside the handleClick function. Like this -
import React, {useState,useEffect} from 'react';
import ReactDOM from 'react-dom';
import axios from 'axios'
function Example() {
const [students,setStudents] = useState('')
const [name,setName] = useState('')
const handleClick = async() => {
const data = await axios.get('api/foo')
setStudents(data)
}
console.log(students)
return (
<div className="container">
<h2>Example component</h2>
<button onClick = {handleClick}>Get students</button>
<div>
{JSON.stringify(students.data)}
</div>
</div>
);
}
export default Example;
if (document.getElementById('root')) {
ReactDOM.render(<Example />, document.getElementById('root'));
}
Initially, the value will be an empty string, then it will change to the value from api/foo
React hooks are async so when you are running console.log(students) right after running setStudents(data) it is still not populated, however the 2nd time you click the button it is already populated from the first time you clicked it.
If you want to console the result right after the state setter runs you can see this answer on another question.
I am trying to learn how to use API's in react. I am making a search input for country names using the Rest countires API. I am getting data from https://restcountries.eu/rest/v2/all but I do not know how to handle this data as I can not use map on an object.
import axios from "axios";
import React, { useEffect, useState } from "react";
const App = () => {
const [countries, setCountries] = useState([]);
const [searchName, setSearchName] = useState("");
useEffect(() => {
axios.get("https://restcountries.eu/rest/v2/all").then(response => {
setCountries(response.data);
});
}, []);
const handleSearch = event => {
setSearchName(event.target.value);
};
return (
<div>
<div>
find countries <input onChange={handleSearch} />
</div>
<div></div>
</div>
);
};
export default App;
Expected to list countries after typing such as : sw = Botswana, Swaziland, Sweden ...
From the question it seems like, these are requirements of your app -
1
you need to search by country name
As you type in, list of countries matching the search should be displayed.
I created this sandbox with the code you provided - https://codesandbox.io/embed/58115762-rest-countries-o638k.
It shows a pair of country name and its capital as you enter input in the search box.
This is how I changed your code:
You need to search countries? - Use search API with country name as value of text input - searchName
https://restcountries.eu/rest/v2/name/${searchName}
To display the output with countries matching your search keyword - map over countries and get appropriate keys. Pass those keys as props to your newly created Country component.
Note, I did not need to change how you handled the JSON response. The searchName and countries are the only two state variables used to render the UI.
you will need to render countries after fetching from ajax request as like :
import axios from "axios";
import React, { useEffect, useState } from "react";
const App = () => {
const [countries, setCountries] = useState([]);
const [searchName, setSearchName] = useState("");
useEffect(() => {
axios.get("https://restcountries.eu/rest/v2/all").then(response => {
setCountries(response.data);
});
}, []);
const handleSearch = event => {
setSearchName(event.target.value);
};
return (
<div>
<div>
find countries <input onChange={handleSearch} />
</div>
<ul>
{(countries.length<=0)?"":
countries.map(country=> <li>country.name</li> )
}
</ul>
</div>
);
};
export default App;
I think this is what you are looking for.
If you have got questions, dont hesitate to ask :)
import axios from "axios";
import React, { useEffect, useState } from "react";
const App = () => {
const [countries, setCountries] = useState([]);
const [searchName, setSearchName] = useState("");
useEffect(() => {
axios.get("https://restcountries.eu/rest/v2/all").then(response => {
setCountries(response.data);
});
}, []);
const handleSearch = event => {
let str = event.target.value;
let filteredCountries = countries.filter((country) => country.name.toLowerCase().includes(str.toLowerCase()));
setCountries(filteredCountries);
setSearchName(str);
};
return (
<div>
<div>
find countries <input onChange={handleSearch} />
</div>
<ul> {(countries.length <= 0) ? "" : countries.map(country => <li>{country.name}</li>) } </ul>
</div>
);
};
export default App;
data =[your array];
countryList = data.map(data=>data.name)
console.log(countryList)