react-search box -> Functionality not working while calling the map function - reactjs

import React, { useState } from "react";
import "./styles.css";
import Heading from "../src/components/Heading";
import Info from "./Info";
import Card from "../src/components/Cards";
function createCard(Info) {
return <Card key={Info.id} name={Info.name} />;
}
function App() {
const [search, setSearch] = useState("");
const findperson = Info.filter( personName=> {
return personName.name.toLowerCase().includes(search.toLowerCase);
});
return (
<div>
<Heading />
<input
type="text"
placeholder="Search"
onChange={event => setSearch(event.target.value)}
/>
{findperson.map(createCard)}
</div>
);
}
export default App;
-----------------------------------------------------------------------------------------
Array info.js
const Info = [
{
id: 1,
name: "Rahul"
},
{
id: 2,
name: "Rohan"
},
{
id: 3,
name: "rajesh"
}
];
export default Info;
I am trying to make a search-box that fetches data from an array (info) and then filters the data , However while calling the map function my code is breaking and i am not able to display any data ,
console log for findPerson

You are not calling toLowerCase, instead you are passing the function itself.
const findperson = Info.filter( personName=> {
// return personName.name.toLowerCase().includes(search.toLowerCase);
return personName.name.toLowerCase().includes(search.toLowerCase()); // <-- () missing
});

I have a hard time trying to understand where the issue is because i cant really debug, but here is a working example as demonstration on how i would do it, maybe that helps.
import React, { useState, useMemo } from "react";
const items = [
{
id: 1,
name: "Hamilton"
},
{
id: 2,
name: "Bruce"
}
];
const Item = ({ name, id }) => {
return <div>{name}</div>;
}
export default function App() {
const [search, setSearch] = useState("");
const filteredItems = useMemo(() => {
return items.filter(i => search === "" || i.name.toLowerCase().includes(search.toLowerCase()))
}, [search]);
return (
<div className="App">
<input value={search} onChange={ e => setSearch(e.target.value)} />
{
filteredItems.map(Item)
}
</div>
);
}

Related

React useState, how to reset to initial value

I have a problem in this code. I need reset item array in line but don't work.
if (!(element.name in filter){setItem([])}
import React, { useState, useEffect, cloneElement } from 'react';
import {fetchData} from '../helpers/fetchData';
function MainElement() {
const [data, setData] = useState([]);
const [filter, setFilter] = useState({});
const [item, setItem] = useState([]);
useEffect( () => {
fetchData("http://localhost/cataleg/php/p3filtres.php")
.then( category => {
setData(category);
});
}, [])
return (
<div onChange={
(e) => {
const product = document.querySelectorAll('input[type="checkbox"]:checked');
[...product].map((element) => {
if (!(element.name in filter){
setItem([]);
}else
setItem([...item, element.value])
return filter[element.name]=item;
})
}
}
>
{
data && Object.entries(data).map((element,i) => {
return (
<div key={i}>
<h3 key={element[0]}>{element[0]}</h3>
{
element[1].map(item => {
return (
Object.entries(item).map((value,i) => {
if (i%2)
return <div key={i}>
<input type="checkbox" key={value[1]} name={element[0]} value={value[1]}/>
<label key={`${value[1]}-${i}`} htmlFor={value[1]}>{value[1]}</label>
</div>
})
)
})
}
</div>
)
})
}
</div>
)
}
export default MainElement;
enter image description here
When ckeckbox is checked I want to store in an object like:
{'processor': [I7,I5], 'RAM':['4GB','8GB']}
So I need reset item array for each key in the object. In may code item array is [I7,I5,'4GB','8GB']
There is a list of things worth fixing in your codes.
onChange has no effect on div
document.querySelector and document.querySelectorAll should not be used inside a React component
Include the <input> checkboxes as part of your app
See useRef if you must reference other elements
.map is being used like .forEach
setItem([...item, element.value]) is called for every element regardless of your condition
filtre is misspelled
item is derived state that should not have its own state
Use data.filter to compute item
See Single Source of Truth
Here is a minimal complete example you can run in your browser.
function fetchProducts() {
return new Promise(resolve =>
setTimeout(resolve, 1000, [
{"name": "carrot", type: "vegetable", quantity: 6 },
{"name": "potato", type: "vegetable", quantity: 0 },
{"name": "pretzels", type: "snack", quantity: 3 }
]))
}
function App() {
const [products, setProducts] = React.useState([])
const [isVegetable, veggieFilter] = useCheckbox(false)
const [isStocked, stockFilter] = useCheckbox(false)
React.useEffect(_ => {
fetchProducts().then(setProducts).catch(console.error)
}, [])
function filter() {
return products
.filter(p => !isVegetable || p.type == "vegetable")
.filter(p => !isStocked || p.quantity > 0)
}
return <div>
Vegetable:{veggieFilter} In-Stock:{stockFilter}
{filter().map(p => <pre>{JSON.stringify(p)}</pre>)}
</div>
}
function useCheckbox(initValue = false) {
const [checked, setChecked] = React.useState(initValue)
return [
checked,
<input type="checkbox" checked={checked} onClick={_ => setChecked(!checked)} />
]
}
ReactDOM.render(<App/>, document.querySelector("#app"))
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.14.0/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.14.0/umd/react-dom.production.min.js"></script>
<div id="app"></div>

passing object using context and doing iteration with map

This is a simple question but I couldn't reach the final result after a lot of attempts. The problem is that I want to pass an object in context and use it in another file. And then do an iteration and create a specific element for each value.
App.jsx
const [activities, setActivity] = useState([
{
key: Math.random() * Math.random(),
name: 'Hello',
}
]);
const inputValue = useRef(null);
const addActivity = () => {
const activity = {
key: Math.random() * Math.random(),
name: inputValue.current.value,
};
setActivity(activities.concat(activity));
};
const value = {
// I want to pass this parameter - only activities has problem (Activity.jsx <h1>)
// I can't achieve activities.name in Activity.jsx
activities: [...activities],
functions: {
addActivity: addActivity
},
ref: {
inputValue: inputValue
}
};
<Context.Provider
value={value}
>
Context.js
export const Context = createContext();
Activity.jsx
const { activities, functions, ref } = useContext(Context);
return (
<section className="activity-container">
<input type="text" ref={ref.inputValue} />
<button onClick={functions.addActivity}>add!</button>
{
activities.map(activity => (
<h1>activity.name</h1>
))
}
</section>
);
I believe this is what you want:
// Sharing data through context
Context file:
// Context.js
import React, { useState, useRef, createContext } from "react";
export const DataContext = createContext();
const getRandom = () => Math.random() * Math.random();
const defaultValue = {
key: getRandom(),
name: "Hello"
};
const ContextProvider = ({ children }) => {
const [activities, setActivity] = useState([defaultValue]);
const inputValue = useRef(null);
const addActivity = () => {
const activity = {
key: getRandom(),
name: inputValue.current.value
};
setActivity([...activities, activity]);
};
const value = {
activities: [...activities],
functions: { addActivity },
ref: { inputValue }
};
return <DataContext.Provider value={value}>{children}</DataContext.Provider>;
};
export default ContextProvider;
Hook to read from context:
// useDataContext
import { useContext } from "react";
import { DataContext } from "./Context";
const useDataContext = () => {
const contextValue = useContext(DataContext);
return contextValue;
};
export default useDataContext;
Child Element where you want to receive the value from context:
// Child.js
import React from "react";
import useDataContext from "./useDataContext";
const Child = () => {
const data = useDataContext();
return (
<>
{data.activities.map((val, idx) => (
<div key={idx}>Name is {val.name}</div>
))}
</>
);
};
export default Child;
And the App container:
// App.js
import Child from "./Child";
import ContextProvider from "./Context";
export default function App() {
return (
<div className="App">
<ContextProvider>
<Child />
</ContextProvider>
</div>
);
}
I've created a sandbox for you to test.
You should make sure that the Activity.jsx component is wrapped with context provider, to get the proper value from the context.
I tried in this codesandbox, and it's working properly. You can refer to this and check what you are missing.

useEffect dosn't save data in localstorage

I have a simple app, sorta for chat purpuses. I fetch data from static file in json format. So this app shows all the messages from that file but also I want to edit the messeges, delete them and add via local storage. For that I used useEffect, but after refresh all the changes I do disappear.
This is my component:
export const WorkChat = (props) => {
const [messageValue, setMessageValue] = useState('');
const [edit, setEdit] = useState(null);
const [editmessageValue, setMessageEditValue] = useState('')
const submitMessage = () => {
const newMessage = {
id: Math.floor(Math.random() * 10000),
message: messageValue
}
props.addMessage(newMessage);
setMessageValue('')
}
const removeMsg = (id) => {
props.deleteMessage(id)
}
const goToEditMode = (message) => {
setEdit(message.id);
setMessageEditValue(message.message)
}
const saveChanges = (id) => {
const newMessagesArray = props.messages.map(m => {
if(m.id === id){
m.message = editmessageValue
}
return m
})
props.updateMessage(newMessagesArray);
setEdit(null)
}
useEffect(()=> {
let data = localStorage.getItem('work-messages');
if(data){
props.setMessages(JSON.parse(data))
}
}, []);
useEffect(()=> {
localStorage.setItem('work-messages', JSON.stringify(props.messages))
},[props.messages])
return (
<div className={s.workChatContainer}>
<input className={s.workInput} placeholder='Enter work message...' onChange={(e)=> setMessageValue(e.target.value)} value={messageValue}/>
<button className={`${s.btn} ${s.sendBtn}`} onClick={()=>submitMessage()}><SendIcon style={{fontSize: 20}}/></button>
<div>
{props.messages.map(m => (
<div key={m.id} className={s.messages}>
{edit !== m.id ? <div>
<span className={s.message}>{m.message}</span>
<button className={`${s.btn} ${s.deleteBtn}`} onClick={()=> removeMsg(m.id)}><DeleteOutlineIcon style={{fontSize: 15}}/></button>
<button className={`${s.btn} ${s.editBtn}`} onClick={()=> goToEditMode(m)}><EditIcon style={{fontSize: 15}}/></button>
</div>
:
<form>
<input className={s.editInput} value={editmessageValue} onChange={(e)=> setMessageEditValue(e.target.value)}/>
<button className={`${s.btn} ${s.saveBtn}`} onClick={()=> saveChanges(m.id)}><BeenhereIcon style={{fontSize: 15}}/></button>
</form>
}
</div>
))}
</div>
</div>
)
}
Just in case, this is my container component:
import { connect } from "react-redux"
import { setFloodMessagesAC, addFloodMessageAC, deleteFloodMessageAC, upadateMessageAC } from "../../redux/flood-reducer"
import { FloodChat } from "./FloodChat"
import { useEffect } from 'react'
import data from '../../StaticState/dataForFlood.json'
const FloodChatApiContainer = (props) => {
useEffect(()=> {
props.setFloodMessages(data)
}, [])
return <FloodChat messages={props.messages}
setFloodMessages={props.setFloodMessages}
addFloodMessage={props.addFloodMessage}
deleteFloodMessage={props.deleteFloodMessage}
upadateMessage={props.upadateMessage}
/>
}
const mapStateToProps = (state) => ({
messages: state.flood.messages
})
export const FloodChatContainer = connect(mapStateToProps, {
setFloodMessages: setFloodMessagesAC,
addFloodMessage: addFloodMessageAC,
deleteFloodMessage: deleteFloodMessageAC,
upadateMessage: upadateMessageAC
})(FloodChatApiContainer)
Why useEffect doesn't work? It seems to me like it should, but it doesnt.
I figured it out. Since I use data from static file, I need to implement functions that get/set data from/to local storage right where I import it which is container component. Once I put those useEffect functions in container component it works perfectly well.
const FloodChatApiContainer = (props) => {
useEffect(()=> {
props.setFloodMessages(data)
}, [])
useEffect(()=> {
let data = JSON.parse(localStorage.getItem('flood-messages'));
if(data){
props.setFloodMessages(data)
}
console.log('get')
}, [])
useEffect(() => {
localStorage.setItem('flood-messages', JSON.stringify(props.messages));
console.log('set')
}, [props.messages]);
return <FloodChat messages={props.messages}
setFloodMessages={props.setFloodMessages}
addFloodMessage={props.addFloodMessage}
deleteFloodMessage={props.deleteFloodMessage}
upadateMessage={props.upadateMessage}
/>
}
const mapStateToProps = (state) => ({
messages: state.flood.messages
})
export const FloodChatContainer = connect(mapStateToProps, {
setFloodMessages: setFloodMessagesAC,
addFloodMessage: addFloodMessageAC,
deleteFloodMessage: deleteFloodMessageAC,
upadateMessage: upadateMessageAC
})(FloodChatApiContainer)

Cant pass array of objects to setPeople function in React

I have another file of data.js that i'm calling it in my index.js. I know I will get data if I pass it in useState directly like this below.
const [people, setPeople] = useState(data);
But I want to keep the above state null at the start rather than passing data.
const [people, setPeople] = useState([]);
But rather than accessing data this way I want to directly pass it in my setPeople. Is it possible to do that? Something like this. So that it can be accessed in people and can be iterated.
setPeople([...people,{data}])
Index.js
import React, { useState, useReducer } from 'react';
import Modal from './Modal';
import { data } from '../../../data';
// reducer function
const Index = () => {
const [name,setName]= useState('');
const [modal, showModal] = useState(false);
const [people, setPeople] = useState([]);
const handleSubmit = ((e)=>
{
e.preventDefault();
setPeople([...people,{data}])
})
return (
<div>
<form onSubmit={handleSubmit}>
<input type='text' value={name} onChange={((e)=>setName(e.target.value))} />
<button type='submit'>Add</button>
</form>
{
people.map((person,index)=>{
return(
<div key={index}>
<h2>{person.name}</h2>
</div>
)
})
}
</div>
)
};
export default Index;
data.js
export const data = [
{ id: 1, name: 'john' },
{ id: 2, name: 'peter' },
{ id: 3, name: 'susan' },
{ id: 4, name: 'anna' },
];
You can pass this additional data to setPeople in the same way which you pass current people - use spread syntax.
setPeople([ ...people, ...data ])
Also if you want to depend on current people state you should pass callback which always provide you correct value which sometimes can't not happen if you depend on people.
setPeople(( prevPeople ) => [ ...prevPeople, ...data ])
UPDATE
const Index = () => {
const [name,setName]= useState('');
const [modal, showModal] = useState(false);
const [people, setPeople] = useState([]);
useEffect( () => {
setState(data) // this will be added only once when component mounted
}, [])
const handleSubmit = ((e)=>{
e.preventDefault();
setPeople(( prevPeople ) => [ ...prevPeople, { id: ?, name: "Only one new person" } ])
})
return (
<div>
{ /* your jsx */ }
</div>
)
};
UPDATE 2
const handleSubmit = () => {
const peoplesIds = people.map( p => p.id )
const notIncludedData = data.filter( obj => ! peoplesIds.includes( obj.id ) )
setPeople( prev => [ ...prev, ...notIncludedData, { id: 100, name: "new guy" } ] )
}
You can try this at handleSubmit
setPeople([...people,...data])

Having React Context in Separate File, Can't Get Component to Not re-render

I've got a simple example of React Context that uses useMemo to memoize a function and all child components re-render when any are clicked. I've tried several alternatives (commented out) and none work. Please see code at stackblitz and below.
https://stackblitz.com/edit/react-yo4eth
Index.js
import React from "react";
import { render } from "react-dom";
import Hello from "./Hello";
import { GlobalProvider } from "./GlobalState";
function App() {
return (
<GlobalProvider>
<Hello />
</GlobalProvider>
);
}
render(<App />, document.getElementById("root"));
GlobalState.js
import React, {
createContext,useState,useCallback,useMemo
} from "react";
export const GlobalContext = createContext({});
export const GlobalProvider = ({ children }) => {
const [speakerList, setSpeakerList] = useState([
{ name: "Crockford", id: 101, favorite: true },
{ name: "Gupta", id: 102, favorite: false },
{ name: "Ailes", id: 103, favorite: true },
]);
const clickFunction = useCallback((speakerIdClicked) => {
setSpeakerList((currentState) => {
return currentState.map((rec) => {
if (rec.id === speakerIdClicked) {
return { ...rec, favorite: !rec.favorite };
}
return rec;
});
});
},[]);
// const provider = useMemo(() => {
// return { clickFunction: clickFunction, speakerList: speakerList };
// }, []);
//const provider = { clickFunction: clickFunction, speakerList: speakerList };
const provider = {
clickFunction: useMemo(() => clickFunction,[]),
speakerList: speakerList,
};
return (
<GlobalContext.Provider value={provider}>{children}</GlobalContext.Provider>
);
};
Hello.js
import React, {useContext} from "react";
import Speaker from "./Speaker";
import { GlobalContext } from './GlobalState';
export default () => {
const { speakerList } = useContext(GlobalContext);
return (
<div>
{speakerList.map((rec) => {
return <Speaker speaker={rec} key={rec.id}></Speaker>;
})}
</div>
);
};
Speaker.js
import React, { useContext } from "react";
import { GlobalContext } from "./GlobalState";
export default React.memo(({ speaker }) => {
console.log(`speaker ${speaker.id} ${speaker.name} ${speaker.favorite}`);
const { clickFunction } = useContext(GlobalContext);
return (
<>
<button
onClick={() => {
clickFunction(speaker.id);
}}
>
{speaker.name} {speaker.id}{" "}
{speaker.favorite === true ? "true" : "false"}
</button>
</>
);
});
Couple of problems in your code:
You already have memoized the clickFunction with useCallback, no need to use useMemo hook.
You are consuming the Context in Speaker component. That is what's causing the re-render of all the instances of Speaker component.
Solution:
Since you don't want to pass clickFunction as a prop from Hello component to Speaker component and want to access clickFunction directly in Speaker component, you can create a separate Context for clickFunction.
This will work because extracting clickFunction in a separate Context will allow Speaker component to not consume GlobalContext. When any button is clicked, GlobalContext will be updated, leading to the re-render of all the components consuming the GlobalContext. Since, Speaker component is consuming a separate context that is not updated, it will prevent all instances of Speaker component from re-rendering when any button is clicked.
Demo
const GlobalContext = React.createContext({});
const GlobalProvider = ({ children }) => {
const [speakerList, setSpeakerList] = React.useState([
{ name: "Crockford", id: 101, favorite: true },
{ name: "Gupta", id: 102, favorite: false },
{ name: "Ailes", id: 103, favorite: true }
]);
return (
<GlobalContext.Provider value={{ speakerList, setSpeakerList }}>
{children}
</GlobalContext.Provider>
);
};
const ClickFuncContext = React.createContext();
const ClickFuncProvider = ({ children }) => {
const { speakerList, setSpeakerList } = React.useContext(GlobalContext);
const clickFunction = React.useCallback(speakerIdClicked => {
setSpeakerList(currentState => {
return currentState.map(rec => {
if (rec.id === speakerIdClicked) {
return { ...rec, favorite: !rec.favorite };
}
return rec;
});
});
}, []);
return (
<ClickFuncContext.Provider value={clickFunction}>
{children}
</ClickFuncContext.Provider>
);
};
const Speaker = React.memo(({ speaker }) => {
console.log(`speaker ${speaker.id} ${speaker.name} ${speaker.favorite}`);
const clickFunction = React.useContext(ClickFuncContext)
return (
<div>
<button
onClick={() => {
clickFunction(speaker.id);
}}
>
{speaker.name} {speaker.id}{" "}
{speaker.favorite === true ? "true" : "false"}
</button>
</div>
);
});
function SpeakerList() {
const { speakerList } = React.useContext(GlobalContext);
return (
<div>
{speakerList.map(rec => {
return (
<Speaker speaker={rec} key={rec.id} />
);
})}
</div>
);
};
function App() {
return (
<GlobalProvider>
<ClickFuncProvider>
<SpeakerList />
</ClickFuncProvider>
</GlobalProvider>
);
}
ReactDOM.render(<App />, document.getElementById("root"));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.13.1/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.13.1/umd/react-dom.production.min.js"></script>
<div id="root"></div>
You can also see this demo on StackBlitz
this will not work if you access clickFuntion in children from provider because every time you updating state, provider Object will be recreated and if you wrap this object in useMemolike this:
const provider = useMemo(()=>({
clickFunction,
speakerList,
}),[speakerList])
it will be recreated each time clickFunction is fired.
instead you need to pass it as prop to the children like this:
import React, {useContext} from "react";
import Speaker from "./Speaker";
import { GlobalContext } from './GlobalState';
export default () => {
const { speakerList,clickFunction } = useContext(GlobalContext);
return (
<div>
{speakerList.map((rec) => {
return <Speaker speaker={rec} key={rec.id} clickFunction={clickFunction }></Speaker>;
})}
</div>
);
};
and for provider object no need to add useMemo to the function clickFunction it's already wrapped in useCallback equivalent to useMemo(()=>fn,[]):
const provider = {
clickFunction,
speakerList,
}
and for speaker component you don't need global context :
import React from "react";
export default React.memo(({ speaker,clickFunction }) => {
console.log("render")
return (
<>
<button
onClick={() => {
clickFunction(speaker.id);
}}
>
{speaker.name} {speaker.id}{" "}
{speaker.favorite === true ? "true" : "false"}
</button>
</>
);
});

Resources