I just implemented a global search in my website and I started having issues with React-Router. It is not updating the view if the url changes parameters.
For example, navigating from /users/454545 to /teams/555555 works as expected. However, navigating from /teams/111111 to teams/222222 changes the url but the component is still /teams/111111.
Here is my code fo the Search Input field.
const SearchResult = ({ id, url, selectResult, text, type }) => (
<Row key={id} onClick={() => selectResult(url)} width='100%' padding='5px 15px 5px 15px' style={{cursor: 'pointer'}}>
<Column alignItems='flex-start' style={{width: '100%'}}>
<Label textAlign='left' color='#ffffff'>{text}</Label>
</Column>
<Column style={{width: '100%'}}>
<Label textAlign='right' color='#ffffff'>{type}</Label>
</Column>
</Row>
)
const SearchInput = (props) => {
const { isSearching, name, onChange, onClear, results } = props;
return (
<Section width='100%' style={{display: 'flex', position: 'relative'}}>
<Wrapper height={props.height} margin={props.margin}>
<i className="fas fa-search" style={{color: 'white'}} />
<input id='search_input' placeholder={'Search for a team, circuit, or user'} name={name} onChange={onChange} style={{outline: 'none', backgroundColor: 'transparent', borderColor: 'transparent', color: '#ffffff', width: '100%'}} />
{onClear && !isSearching && <i onClick={onClear} className="fas fa-times-circle" style={{color: '#50E3C2'}} />}
{isSearching &&
<Spinner viewBox="0 0 50 50" style={{marginBottom: '0px', height: '50px', width: '50px'}}>
<circle
className="path"
cx="25"
cy="25"
r="10"
fill="none"
strokeWidth="4"
/>
</Spinner>
}
</Wrapper>
{results && <Section backgroundColor='#00121A' border='1px solid #004464' style={{maxHeight: '400px', position: 'absolute', top: '100%', left: '0px', width: '97%', overflowY: 'scroll'}}>
<Section backgroundColor='#00121A' style={{display: 'flex', flexDirection: 'column', padding: '15px 0px 0px 0px', justifyContent: 'center', alignItems: 'center', width: '100%'}}>
{results.length === 0 && <Text padding='0px 0px 15px 0px' color='#ffffff' fontSize='16px'>We didn't find anything...</Text>}
{results.length !== 0 && results.map(r => <SearchResult selectResult={props.selectResult} id={r._id} url={r.url} text={r.text} type={r.type} />)}
</Section>
</Section>}
</Section>
)
}
export default SearchInput;
The parent component is a nav bar which looks something like this. I've slimmed it down for readability.
import React, { useState, useEffect } from 'react';
import { connect } from 'react-redux';
import SearchInput from '../shared/inputs/SearchInput';
const TopNav = (props) => {
const [search, setSearch] = useState(null);
const [searchResults, setSearchResults] = useState(null);
const debouncedSearchTerm = useDebounce(search, 300);
const [isSearching, setIsSearching] = useState(false);
function clearSearch() {
document.getElementById('search_input').value = '';
setSearchResults(null);
}
function searchChange(e) {
if (!e.target.value) return setSearchResults(null);
setSearch(e.target.value);
setIsSearching(true);
}
async function updateQuery(query) {
const data = {
search: query
}
const results = await api.search.query(data);
setSearchResults(results);
setIsSearching(false);
}
function selectResult(url) {
props.history.push(url);
setSearchResults(null);
}
function useDebounce(value, delay) {
// State and setters for debounced value
const [debouncedValue, setDebouncedValue] = useState(value);
useEffect(
() => {
// Update debounced value after delay
const handler = setTimeout(() => {
setDebouncedValue(value);
}, delay);
// Cancel the timeout if value changes (also on delay change or unmount)
// This is how we prevent debounced value from updating if value is changed ...
// .. within the delay period. Timeout gets cleared and restarted.
return () => {
clearTimeout(handler);
};
},
[value, delay] // Only re-call effect if value or delay changes
);
return debouncedValue;
}
useEffect(() => {
if (debouncedSearchTerm) {
updateQuery(debouncedSearchTerm);
} else {
setSearchResults(null);
}
}, [user, debouncedSearchTerm])
return (
<ContentContainer style={{boxShadow: '0 0px 0px 0 #000000', position: 'fixed', zIndex: 1000}} backgroundColor='#00121A' borderRadius='0px' width='100%'>
<Section style={{display: 'flex', justifyContent: 'center', alignItems: 'center', height: '50px'}} width='1200px'>
<SearchInput height={'30px'} margin='0px 20px 0px 0px' isSearching={isSearching} selectResult={selectResult} onChange={searchChange} onClear={clearSearch} results={searchResults} />
</Section>
</ContentContainer>
)
}
function mapStateToProps(state) {
return {
user: state.user.data,
notifs: state.notifs
}
}
export default connect(mapStateToProps, { logout, fetchNotifs, updateNotifs })(TopNav);
Tl;DR
Using react-router for site navigation. Doesn't update component if navigating from /teams/111111 to /teams/222222 but does update if navigating from /users/111111 to /teams/222222.
Any and all help appreciated!
When a URL's path changes, the current Component is unmounted and the new component pointed by the new URL is mounted. However, when a URL's param changes, since the old and new URL path points to the same component, no unmount-remount takes place; only the already mounted component receives new props. One can make use of these new props to fetch new data and render updated UI.
Suppose your param id is parameter.
With hooks:
useEffect(() => {
// ... write code to get new data using new prop, also update your state
}, [props.match.params.parameter]);
With class components:
componentDidUpdate(prevProps){
if(this.props.match.params.parameter!== prevProps.match.params.parameter){
// ... write code to get new data using new prop, also update your state
}
}
Use KEY:
Another approach could be to use the unique key prop. Passing a new key will force a
component to remount.
<Route path="/teams/:parameter" render={(props) => (
<Team key={props.match.params.parameter} {...props} />
)} />
Re-render does not cause component to re-mount so use useEffect hook to call initializing logic in your component whenever props changes and update your state in the callback.
useEffect(() => {
//Re initialize your component with new url parameter
}, [props]);
Related
I am trying to make a game in react-native. I want to render 200+ views on the Game screen. Each View has a pressable functionality. Whenever I press the View I need to run a function that will change the View background color and update score on the game context. But Whenever I try to press any View it took some time to change the background and update the context.
Note
I am using the expo as a development environment and I am using a real device too.
My View Component
import { useEffect, useState, memo } from "react";
import { useContext } from "react";
import { gameContext } from "./gameContext";
import { Pressable, View } from "react-native";
function CheckBoxCom() {
const [active, setActive] = useState(false);
const { score, setScore } = useContext(gameContext);
useEffect(() => {
let time = setTimeout(() => {
setActive(false);
}, Math.floor(Math.random() * 35000));
return () => clearTimeout(time);
}, [active]);
const handlePress = () => {
if (active) return;
setActive(true);
setScore(score + 1);
};
return (
<View>
<Pressable onPress={handlePress}>
<View
style={{
width: 20,
height: 20,
borderWidth: 2,
borderColor: active ? "green" : "gray",
margin: 3,
borderRadius: 3,
backgroundColor: active ? "green" : null,
}}
></View>
</Pressable>
</View>
);
}
export default memo(CheckBoxCom);
Game Screen Component
import { useContext, useEffect, useState } from "react";
import { StatusBar } from "expo-status-bar";
import { StyleSheet, Text, View, FlatList } from "react-native";
import CheckBox from "./CheckBox";
import { gameContext } from "./gameContext";
export default function Game({ navigation }) {
const { score, time, setTime, boxList } = useContext(gameContext);
const [intervalId, setIntervalId] = useState("");
useEffect(() => {
const int = setInterval(() => {
setTime((prvTime) => prvTime - 1);
}, 1000);
setIntervalId(int);
return () => clearInterval(int);
}, []);
if (time === 0) {
clearInterval(intervalId);
navigation.navigate("Score", { score });
}
return (
<View style={{ flex: 1 }}>
<StatusBar style="auto" />
<View style={styles.textHeader}>
<Text>Score : {score}</Text>
<Text>Time Left: {time}s</Text>
</View>
<View style={styles.checkBoxContainer}>
<FlatList
style={{ alignSelf: "center" }}
data={boxList}
initialNumToRender={50}
numColumns={12}
renderItem={(i) => <CheckBox />}
keyExtractor={(i) => i.toString()}
scrollEnabled={false}
/>
</View>
</View>
);
}
const styles = StyleSheet.create({
textHeader: {
display: "flex",
flexDirection: "row",
justifyContent: "space-between",
width: "100%",
marginTop: 40,
paddingHorizontal: 30,
},
checkBoxContainer: {
margin: 20,
display: "flex",
flexWrap: "wrap",
height: "80%",
overflow: "hidden",
flexDirection: "row",
},
});
How can I run view function immediately whenever I press it?
The reason it is slow is that when you press on a view, all 200+ CheckBoxCom components rerender. If they don't need to, we can improve performance by trying to prevent those unnecessary rerenders.
I believe the major bottleneck here is the gameContext. It groups together a lot of states and if any of these were to change, all components will rerender. It provides score state that you are reading within each CheckBoxCom. Whenever the score changes all CheckBoxCom components will re-render. If you change handlePress() to:
const handlePress = () => {
if (active) return;
setActive(true);
setScore(score => score + 1);
};
Please note the use of callback to update the score in the above handler. In this case, we don't need to read score from context, so we can remove it from the game context provider, only pass setScore. Removing score from the context provider is important because not doing so will rerender all components using the context even if you don't specifically destructure score.
Also, make sure you don't have a lot of state variables within a single context. Split it into multiple contexts if you have different states in there. In this way, you will be able to reduce unnecessary rerenders of the CheckBoxCom components.
Since your CheckBoxCom components have an internal state, using React.memo() will not help to prevent rerenders because it only works for rerenders resulting from changed props.
But if you are able to refactor them to lift the active state up to the parent i.e. something like activeViews or something (which could be a map of indexes which are true i.e. active), then you can pass the active state as a boolean prop to each CheckBoxCom component. And if we also pass setScore via a prop instead of via context, we can benefit from React.memo(). BTW it is not necessary to wrap setState methods with useCallback().
The end result will be: CheckBoxCom components with zero internal states and no reliance on context, in other words, pure components i.e. components which work nicely with React.memo().
Use pagination in flatlist
for ref: Pagination in flatlist
import React, { Component } from 'react';
import {
View,
Text,
TouchableOpacity,
StyleSheet,
FlatList,
Platform,
ActivityIndicator,
} from 'react-native';
export default class App extends Component {
constructor() {
super();
this.state = {
loading: true,
//Loading state used while loading the data for the first time
serverData: [],
//Data Source for the FlatList
fetching_from_server: false,
//Loading state used while loading more data
};
this.offset = 0;
//Index of the offset to load from web API
}
componentDidMount() {
//fetch('http://aboutreact.com/demo/getpost.php?offset=' + this.offset)
fetch('https://www.doviz.com/api/v1/currencies/all/latest')
.then(response => response.json())
.then(responseJson => {
responseJson = responseJson.slice((this.offset*12),((this.offset+1)*12)-1)
console.log("offset : "+this.offset);
console.log(responseJson.slice((this.offset*12),((this.offset+1)*12)-1));
//Successful response from the API Call
this.offset = this.offset + 1;
//After the response increasing the offset for the next API call.
this.setState({
// serverData: [...this.state.serverData, ...responseJson.results],
serverData: [...this.state.serverData, ...responseJson],
//adding the new data with old one available in Data Source of the List
loading: false,
//updating the loading state to false
});
})
.catch(error => {
console.error(error);
});
}
loadMoreData = () => {
//On click of Load More button We will call the web API again
this.setState({ fetching_from_server: true }, () => {
//fetch('http://aboutreact.com/demo/getpost.php?offset=' + this.offset)
fetch('https://www.doviz.com/api/v1/currencies/all/latest')
.then(response => response.json())
.then(responseJson => {
responseJson = responseJson.slice((this.offset*12),((this.offset+1)*12)-1)
console.log("offset Load : "+this.offset);
console.log(responseJson);
//Successful response from the API Call
this.offset = this.offset + 1;
//After the response increasing the offset for the next API call.
this.setState({
//serverData: [...this.state.serverData, ...responseJson.results],
serverData: [...this.state.serverData, ...responseJson],
fetching_from_server: false,
//updating the loading state to false
});
})
.catch(error => {
console.error(error);
});
});
};
renderFooter() {
return (
//Footer View with Load More button
<View style={styles.footer}>
<TouchableOpacity
activeOpacity={0.9}
onPress={this.loadMoreData}
//On Click of button calling loadMoreData function to load more data
style={styles.loadMoreBtn}>
<Text style={styles.btnText}>Loading</Text>
{this.state.fetching_from_server ? (
<ActivityIndicator color="white" style={{ marginLeft: 8 }} />
) : null}
</TouchableOpacity>
</View>
);
}
render() {
return (
<View style={styles.container}>
{this.state.loading ? (
<ActivityIndicator size="large" />
) : (
<FlatList
style={{ width: '100%' }}
keyExtractor={(item, index) => index}
data={this.state.serverData}
renderItem={({ item, index }) => (
<View style={styles.item}>
<Text style={styles.text}>
{item.currency}
{'.'}
{item.code}
</Text>
</View>
)}
onEndReached={this.loadMoreData}
onEndReachedThreshold ={0.1}
ItemSeparatorComponent={() => <View style={styles.separator} />}
ListFooterComponent={this.renderFooter.bind(this)}
//Adding Load More button as footer component
/>
)}
</View>
);
}
}
const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
paddingTop: 30,
},
item: {
padding: 10,height:80
},
separator: {
height: 0.5,
backgroundColor: 'rgba(0,0,0,0.4)',
},
text: {
fontSize: 15,
color: 'black',
},
footer: {
padding: 10,
justifyContent: 'center',
alignItems: 'center',
flexDirection: 'row',
},
loadMoreBtn: {
padding: 10,
backgroundColor: '#800000',
borderRadius: 4,
flexDirection: 'row',
justifyContent: 'center',
alignItems: 'center',
},
btnText: {
color: 'white',
fontSize: 15,
textAlign: 'center',
},
});
I have a CountryList react component
import React from "react";
import { Link } from "react-router-dom";
import { BsSearch } from "react-icons/bs";
export default function CountryList({
countries,
}: {
countries: any;
}): JSX.Element {
const [filter, setFilter] = React.useState("");
const [sortType, setSortType] = React.useState("");
console.log(filter);
const sorted = countries.sort((a: { name: string }, b: { name: any }) => {
const isReversed = sortType === "asc" ? 1 : -1;
return isReversed * a.name.localeCompare(b.name);
});
const onSort = (sortType: React.SetStateAction<string>) => {
console.log("changed");
setSortType(sortType);
};
return (
<div style={{ marginTop: "3rem" }}>
<div
style={{
display: "flex",
justifyContent: "space-between",
marginBottom: "10px",
}}
>
<div>List of countries</div>
<div style={{ display: "flex", alignItems: "center" }}>
<div style={{ position: "relative", marginRight: "1rem" }}>
<input
type="text"
placeholder="Filter"
name="namePrefix"
style={{ padding: "0.35rem" }}
onChange={(e: any) => {
setFilter(e.target.value);
}}
/>
<div style={{ position: "absolute", top: "5px", right: "5px" }}>
<BsSearch size="16" />
</div>
</div>
<div style={{ width: "8rem" }}>
<div className="btn-group">
<button
type="button"
className="btn dropdown-toggle sort-button"
data-bs-toggle="dropdown"
aria-expanded="false"
>
{sortType === "asc"
? "Ascending"
: sortType === "desc"
? "Descending"
: "Select"}
</button>
<ul className="dropdown-menu sort-button">
<li>
<button
className="dropdown-item"
type="button"
onClick={() => onSort("asc")}
>
Ascending
</button>
</li>
<li>
<button
className="dropdown-item"
type="button"
onClick={() => onSort("desc")}
>
Descending
</button>
</li>
</ul>
</div>
</div>
</div>
</div>
<div className="country-list-items">
{countries &&
sorted.map((item: any, index: number) => (
<div key={index}>
<Link style={{ display: "block" }} to={`/regions`}>
{item.name}
</Link>
</div>
))}
</div>
<div
style={{ marginTop: "20px", display: "flex", justifyContent: "center" }}
>
{countries && countries.length > 10 ? (
<button className="secondary-button">Load More</button>
) : (
<p>There are no more countries</p>
)}
</div>
</div>
);
}
Now from this component I need to pass the data of selected country id while the user clicks on the Link of the respective country, which I will be able to get by {item.code}. Also on clicking the Link the user will be redirected to /regions route where the list of regions of the selected country from this component will be shown. This is the RegionList Component:
import React from "react";
import { Link } from "react-router-dom";
import { BsSearch } from "react-icons/bs";
export default function RegionList(): JSX.Element {
return (
<div style={{ marginTop: "3rem" }}>
<div
style={{
display: "flex",
justifyContent: "space-between",
marginBottom: "10px",
}}
>
<div>List of regions</div>
<div style={{ display: "flex", alignItems: "center" }}>
<div style={{ position: "relative", marginRight: "1rem" }}>
<input
type="text"
placeholder="Filter"
style={{ padding: "0.35rem" }}
/>
<div style={{ position: "absolute", top: "5px", right: "5px" }}>
<BsSearch size="16" />
</div>
</div>
<div style={{ width: "8rem" }}>
<select name="sort" id="sort">
<option value="asc">Ascending</option>
<option value="desc">Descending</option>
</select>
</div>
</div>
</div>
<div className="country-list-items">
<div>
<Link style={{ display: "block" }} to={`/cities`}>
Alaska
</Link>
</div>
</div>
<div
style={{ marginTop: "20px", display: "flex", justifyContent: "center" }}
>
<button className="secondary-button">Load More</button>
<p>There are no more countries</p>
</div>
</div>
);
}
I need to pass the country id from the CountryList component to this RegionList component because I will do a GET network call in the RegionList component using the selected country id passed from the CountryList component. But I am not able to pass the country id data from CountryList component to RegionList component as they are on different routes and they do not have any common parent component. This is the route file for Countries
import { Route, Routes } from "react-router-dom";
import React from "react";
import CountryComponent from "../components/CountryComponent";
export class CountryRoute extends React.Component {
render() {
return (
<Routes>
<Route path="/" element={<CountryComponent />} />
</Routes>
);
}
}
here <CountryComponent /> is the mother component of CountryList
This is the route file for Regions:
import { Route, Routes } from "react-router-dom";
import React from "react";
import RegionComponent from "../components/RegionComponent";
export class RegionsRoute extends React.Component {
render() {
return (
<Routes>
<Route path="/" element={<RegionComponent />} />
</Routes>
);
}
}
here <RegionComponent /> is the mother component of RegionList
Here is the Main Component where all the components are called
import React from "react";
import { Routes, Route } from "react-router-dom";
import { ToastContainer } from "react-toastify";
import "react-toastify/dist/ReactToastify.css";
import styled from "styled-components";
import "styled-components/macro";
import { CountryRoute } from "../country/route";
import { RegionsRoute } from "../region/route";
import { CitiesRoute } from "../cities/route";
const MainContainer = styled.div`
min-height: 100%;
margin: 5rem;
`;
export const Main = (): JSX.Element => {
return (
<>
<>
<MainContainer>
<div style={{ textAlign: "center" }}>
<b>GEO SOFTWARE</b>
</div>
<div>
<div>
<Routes>
<Route path={"/countries*"} element={<CountryRoute />} />
<Route path={"/regions*"} element={<RegionsRoute />} />
<Route path={"/cities*"} element={<CitiesRoute />} />
</Routes>
</div>
</div>
<ToastContainer
toastClassName={"toastContainer e-12"}
hideProgressBar
position="bottom-left"
closeButton={false}
autoClose={5000}
bodyClassName={"toastBody"}
/>
</MainContainer>
</>
</>
);
};
Now how can I pass the selected country code data from CountryList to the RegionList component.
You can use Query Params for this. In the CountryList you can use the Link like this:
<Link style={{ display: "block" }} to={`/regions?country=COUNTRY_ID`}>
Then in the RegionsList youn can get that Query Parameter from the url and use as you want.
Check this example https://reactrouter.com/web/example/query-parameters
You could set up a simple "store" to keep track of the selected country independently of your component hierarchy.
The simplest possible store
A stripped down, simplest implementation possible might look something like this:
const data = {}
export default {
setCountry: c => data.country = c,
getCountry: () => data.country
}
Because the "store" data is a singleton, any component that imports the store will get the same info, regardless of where it is in the component tree.
import store from './store';
export default () => (
<div>{store.getCountry()}</div>
)
Listening for changes, etc.
The example above omits some details that may be important, depending on what you're doing, like updating views that have already rendered when the country value changes.
If you need that sort of thing you could make the store an event emitter so your components can listen for updates:
import Emitter from 'events';
class CountryStore extends Emitter {
data = {}
getCountry () {
return this.data.country;
}
setCountry (c) {
this.data.country = c;
this.emit('change'); // notify interested parties of the change
}
}
export default new CountryStore();
With the emitter in place, components can register for change notifications when they mount:
import store from './store';
function SomeComponent () {
useEffect(() => {
store.on('change', () => {
// do stuff when store changes happen
}, [])
})
return (<div>...</div>)
}
Custom Hook
To make it easy to do this wherever its needed you could wrap it all up in a custom hook that handles it all and returns the current value and a setter [country, setCountry] just like useState would:
const useCountry = () => {
const [country, setCountry] = useState(store.getCountry());
const handler = () => setCountry(store.getCountry());
useEffect(() => {
store.on('change', handler);
return () => store.off('change', handler);
})
return [country, c => store.setCountry(c)];
}
Then your components have it easy:
import useCountry from './useCountry.js';
export default function SomeComponent () {
const [country, setCountry] = useCountry();
return (
<div>
<div>Current Country: {country}</div>
<button onClick={() => setCountry(Math.random())}>Change Country</button>
</div>
)
}
There are off-the-shelf libraries that will do all of this and more for you, but I thought it might be more helpful to explain an actual rudimentary implementation.
You can have some sort of global state country_id which is initially equal to null.
When user clicks on a country, set that country_id to be equal to the clicked country id.
Now, Inside you RegionList component you can access the country id through country_id state.
You can achieve the state management by different ways:
Prop drilling
Context API
Use Redux or Recoil to handle state-management
As others have pointed out, this is 100% what context is for.
It looks like this:
import React, { createContext, useContext } from 'react';
const MyCountryContext = createContext(null);
export const useCountry = () => useContext(MyCountryContext);
export const MyCountryContext = ({children}) => {
const [country,setCountry] = useState();
return (
<MyCountryContext.Provider value={[country,setCountry]}>
{children}
</MyCountryContext.Provider>
)
}
Use it like this:
export const Main = (): JSX.Element => {
return (
<MyCountryContext>
...rest of your tree
</MyCountryContext>
);
}
Then, in any components that are below MyCountryContext you can use the hook just like useState:
import { useCountry } from './MyCountryContext';
const MyComponentThatUsesCountry = () => {
const [country,setCountry] = useCountry();
return (...)
}
I am trying to pass function as prop. I did this before but now with the same logic it is giving me error (this.props.functionName is not a function).
I have a child (Navbar) and a parent component(MainComponent). I want to send a input value from Navbar to MainComponet and set it to the state value in parent Component.
Parent Component
import React ,{Component}from 'react'
import Navbar from '../Presentational/Navbar'
class Main extends Component{
constructor(props){
super(props)
this.state = {
searchItem: ''
}
}
GetSearchItem(search){
this.setState({searchItem:search})
}
render(){
return(
<div className = 'container'>
<div className = 'row'>
<div className = 'col-12 mt-1'>
<Navbar onChange = {(search)=>this.GetSearchItem(search)}></Navbar>
</div>
</div>
<div className = 'row'>
<div className = 'col-3'>
<h3>{this.state.searchItem}</h3>
</div>
</div>
</div>
)
}
}
export default Main
Child Component (Navbar)
import React,{Component} from 'react'
import {AppBar,Toolbar,IconButton,Typography,InputBase} from '#material-ui/core'
import MenuIcon from '#material-ui/icons/Menu';
import SearchIcon from '#material-ui/icons/Search';
import {fade , makeStyles} from '#material-ui/core/styles'
const useStyles = makeStyles((theme) => ({
root: {
flexGrow: 1,
},
menuButton: {
marginRight: theme.spacing(2),
},
title: {
flexGrow: 1,
display: 'none',
[theme.breakpoints.up('sm')]: {
display: 'block',
},
},
search: {
position: 'relative',
borderRadius: theme.shape.borderRadius,
backgroundColor: fade(theme.palette.common.white, 0.15),
'&:hover': {
backgroundColor: fade(theme.palette.common.white, 0.25),
},
marginLeft: 0,
width: '100%',
[theme.breakpoints.up('sm')]: {
marginLeft: theme.spacing(1),
width: 'auto',
},
},
searchIcon: {
padding: theme.spacing(0, 2),
height: '100%',
position: 'absolute',
pointerEvents: 'none',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
},
inputRoot: {
color: 'inherit',
},
inputInput: {
padding: theme.spacing(1, 1, 1, 0),
// vertical padding + font size from searchIcon
paddingLeft: `calc(1em + ${theme.spacing(4)}px)`,
transition: theme.transitions.create('width'),
width: '100%',
[theme.breakpoints.up('sm')]: {
width: '12ch',
'&:focus': {
width: '20ch',
},
},
},
}));
class Navbar extends Component{
render(){
const classes = this.props.classes;;
return(
<div className={classes.root}>
<AppBar position="static">
<Toolbar>
<IconButton
edge="start"
className={classes.menuButton}
color="inherit"
aria-label="open drawer"
>
<MenuIcon />
</IconButton>
<Typography className={classes.title} variant="h6" noWrap>
Pizaa Valley
</Typography>
<div className={classes.search}>
<div className={classes.searchIcon}>
<SearchIcon />
</div>
<InputBase
placeholder="Search…"
classes={{
root: classes.inputRoot,
input: classes.inputInput,
}}
inputProps={{ 'aria-label': 'search' }}
onChange={(event)=>this.props.onChange(event.target.value)}
/>
</div>
</Toolbar>
</AppBar>
</div>
)
}
}
export default () => {
const classes = useStyles();
return (
<Navbar classes={classes} />
)
}
The problem is that you have two Navbar types. You first have the class component created using class Navbar. And second you have the following functional component defined here:
export default () => {
const classes = useStyles();
return (
<Navbar classes={classes} />
)
}
When you do
import Navbar from '../Presentational/Navbar'
<Navbar onChange = {(search)=>this.GetSearchItem(search)}></Navbar>
The onChange prop is correctly given to the functional component, but is never passed along to the class-based component. You can fix this by replacing your functional component with the below code:
export default props => {
const classes = useStyles();
return (
// using the "spread operator", we pass along all the props given
// to the functional component, so the class-based component can
// also access these
<Navbar {...props} classes={classes} />
)
}
you've done everything correctly except change this:
GetSearchItem(search){
this.setState({searchItem:search})
}
to
GetSearchItem = (search) => {
this.setState({searchItem:search})
}
as an arrow function it has access to the scope above
Try with the following:-
In your parent component modified the below line:-
<Navbar onChangeCallBack = {(search)=>this.GetSearchItem(search)}></Navbar>
In your child Navbar component only modified the below line:-
onChange={(event)=>this.props.onChangeCallBack(event.target.value)}
is it possible to render a React.Component over other React.Component using just fat arrow function, using state seems unnecessary in my case as there is no need to close the opened Component. I am trying to achieve the simplest to render a React.Component over other React.Component.
I am trying to do it like this:
<Button onPress={() => { return (<ShowOtherReactComponent/>); }} >Show OtherComponent</Button>
this is calling the <ShowOtherReactComponent/> I know that because I called an alert function from constructor but! nothing is rendering. why is that? how can I do this?
PS: this approach may be wrong, but still wanna see how it can be done. for science.
You shouldn't return jsx from your handlers. Usually to show and or toggle components conditional rendering is the way to go.
Instead of returning <ShowOtherReactComponent/> from onPress you conditionally render the component based on a boolean binded to the local state and change the state instead.
const Component = () =>{
const [show, setShow] = useState(false)
const onPress = () => setShow(true)
return(
<>
<button onPress={onPress}> Show </button>
{ show && <ShowOtherReactComponent/> }
</>
)
}
I've made an example to show what you could potentially do if you wanted a button to add components to display:
import React from 'react';
import autoBind from 'react-autobind';
export default class ButtonTest extends React.Component {
constructor(props) {
super(props);
this.state = {
extraComponents : []
};
autoBind(this);
}
addComponent() {
const newComponent = (<p>I'm a new component</p>);
this.setState({extraComponents: [...this.state.extraComponents, newComponent]})
}
render() {
return (
<div>
<button onClick={this.addComponent}>add component</button>
{this.state.extraComponent}
</div>
)
}
}
I've checked it and it works.
import React, { useState } from 'react'
import { SafeAreaView, View, Text, Button, Dimensions } from 'react-native'
const App = () => {
const [visibilityOfOtherView, setvisibilityOfOtherView] = useState(false)
const { height, width } = Dimensions.get('window')
const SCREEN_HEIGHT = Math.round(height)
const SCREEN_WIDTH = Math.round(width)
return (
<SafeAreaView style={{ height: SCREEN_HEIGHT, width: SCREEN_WIDTH, }}>
<View style={{ flex: 1, alignItems: 'center', justifyContent: 'center', backgroundColor: 'red' }}>
<Text style={{ marginBottom: 20 }}>
First Components
</Text>
<Button
title='Toggle Components View'
onPress={() => setvisibilityOfOtherView(!visibilityOfOtherView)}
/>
</View>
{
visibilityOfOtherView ?
<View style={{ height: SCREEN_HEIGHT, width: SCREEN_WIDTH, alignItems: 'center', justifyContent: 'center', backgroundColor: 'green' }}>
<Text style={{ marginBottom: 20 }}>
Secound Components
</Text>
<Button
title='Toggle Components View'
onPress={() => setvisibilityOfOtherView(!visibilityOfOtherView)}
/>
</View>
: null
}
</SafeAreaView>
)
}
export default App
I'm getting data from API into FlatList in my expo app but the issue is there is more than 500+ records but I want to show only 10 records in FlatList so is there any way to do that?
export default class HomeScreen extends Component {
constructor() {
super()
this.state = {
itemList: []
}
}
componentWillMount() {
axios.get('myApiUri')
.then((response) => {
this.setState({
itemList: response.data
});
console.log("Data", response.data)
})
.catch(error => console.log("Errorrr:" + error))
}
// renderItem(data) {
// return (
// );
// }
render() {
return (
<Container style={{ flex: 1, flexDirection: 'row' }}>
<FlatList
data={this.state.itemList}
// columnWrapperStyle={{ justifyContent: "space-around", flex: 1 }}
maxToRenderPerBatch={10}
horizontal
renderItem={({ item }) => {
return (
<View style={{ width: 150, paddingHorizontal: 3 }}>
<Card style={{ height: 200, padding:0 }}>
<CardItem>
<Body>
<Image source={{ uri: item.MainImg }} style={{ width: '100%', height: 150 }} />
{item.ItemName.length > 10 ? <Text style={style.productTitle} numberOfLines={1}>{item.ItemName.substring(0,18) + '...'}</Text> : <Text numberOfLines={1} style={style.productTitleLess}>{item.ItemName}</Text>}
</Body>
</CardItem>
</Card>
</View>
);
}}
keyExtractor={item => item.ID}
/>
</Container>
);
}
}
By slicing the array:
<FlatList
data={this.state.itemList.slice(0, 10)}
When you have a lot of data to show in a list, especially when it's around 500+, it is always good to maintain it as a paginated response from the backend; and then pass only up to 10 values to the FlatList component.
What you can do here, in your case is, paginate it from the frontend.
Your constructor will be like,
constructor() {
super()
this.state = {
itemList: [],
pages: 0,
currentPage: 0
}
}
And your componentWillMount axios call will have a callback like,
(response) => {
const pages = response.data / 10;
this.setState({
itemList: response.data,
pages
}
);
Now that you have data in the state, let's render it into FlatList component. In render(),
const { itemList, currentPage } = this.state;
const startingIndex = currentPage + 10;
const endingIndex = startingIndex + 10;
const data = itemList.slice(startingIndex, endingIndex);
And pass the data to FlatList like data={data}.
You'll need to have a paginated button component, and for which you will have a onChange functionality which can be maintained like,
handlePageChange = (currentPage) => {
this.setState({
currentPage
})
}
P.S. here, the index based on the page is maintained assuming the page number will be maintained on 0 indexing.