Searchfield based on the data in ReactJS - javascript

I'm a newbie in React. I'm creating a little app that has
users and I need to create a searchbar that will look for users if I
type two or three lettters of users name. But obviously I stuck. So
any help will be nice. Thanks in advance
import React, { Component } from 'react'
class FilterForm extends Component {
state = {
query: '',
user: [],
searchString:[]
}
handleInputChange = (e) => {
this.setState ({
query:e.target.value
} ,()=>{
this.filterArray();
})
}
getData = () => {
fetch(`http.//localhost:3000/login`)
.then(response => response.json())
.then(responseData => {
//console.log(responseData)
this.setState ({
user:responseData,
searchString: responseData
})
})
}
filterArray = () => {
let searchString = this.state.query;
let responseData = this.state.user;
if(searchString.length > 0){
//console.log(responseData[i].first_name)
searchString = responseData.filter(searchString);
this.setState({ responseData })
}
}
componentWillMount() {
this.getData();
}
render() {
return(
<div className="searchform">
<form>
<input
type="text"
id="filter"
placeholder="Search for user..."
onChange={this.handleInputChange}/>
</form>
<div>{this.state.searchString.map(i => <p>{i.first_name}</p>)}
</div>
</div>
)
}
}
export default FilterForm
And this is my App.js
import React from 'react';
import PeopleList from "./components/PeopleList"
import FilterForm from "./components/FilterForm"
//import { search } from "./Utils"
//import UserData from "./components/UserData";
//import SearchBox from "./components/SearchBox"
import './App.css';
class App extends React.Component {
render() {
return (
<React.Fragment>
<div>
<FilterForm />
<PeopleList />
</div>
</React.Fragment>
);
}
}
export default App
And when I start typing something in searchbar I get an error:

I edit your post and did some indentation, and to your question, your problem is in fillterArray, this is not how you use filter method and you are setting state to user witch is not relevant to the search.
try this:
filterArray = () => {
const { query, user } = this.state;
if(query.length > 0){
const searchResult = searchString.filter((el, i) => el[i].first_name.includes(query);
this.setState({ searchString: searchResult })
}
}
more info about filter method:
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/filter

Related

Getting component to re-render after form submit. Using useContext and custom hooks to fetch data

I'm having some issues getting a component to re-render after submitting a form. I created separate files to store these custom hooks to make them as reusable as possible. Everything is functioning correctly, except I haven't figured out a way to re render a list component after posting a new submit to that list. I am using axios for fetch requests and react-final-form for my actual form. Am I not able to re-render the component because I am using useContext to "share" my data across child components? My comments are set up as nested attributes to each post, which is being handled through Rails. My comment list is rendered in it's own component, where I call on the usePost() function in the PostContext.js file. I can provide more info/context if needed.
**
Also, on a slightly different note. I am having difficulty clearing the form inputs after a successful submit. I'm using react-final-form and most the suggestions I've seen online are for class components. Is there a solution for functional components?
react/contexts/PostContext.js
import React, { useContext, useState, useEffect } from "react";
import { useParams } from "react-router-dom";
import { useAsync } from "../hooks/useAsync";
import { getPost } from "../services/post";
const Context = React.createContext();
export const usePost = () => {
return useContext(Context);
};
export const PostProvider = ({ children }) => {
const id = useParams();
const { loading, error, value: post } = useAsync(() => getPost(id.id), [
id.id,
]);
const [comments, setComments] = useState([]);
useEffect(() => {
if (post?.comments == null) return;
setComments(post.comments);
}, [post?.comments]);
return (
<Context.Provider
value={{
post: { id, ...post },
comments: comments,
}}
>
{loading ? <h1>Loading</h1> : error ? <h1>{error}</h1> : children}
</Context.Provider>
);
};
react/services/comment.js
import { makeRequest } from "./makeRequest";
export const createComment = ({ message, postId }) => {
message["post_id"] = postId;
return makeRequest("/comments", {
method: "POST",
data: message,
}).then((res) => {
if (res.error) return alert(res.error);
});
};
react/services/makeRequest.js
import axios from "axios";
const api = axios.create({
baseURL: "/api/v1",
withCredentials: true,
});
export const makeRequest = (url, options) => {
return api(url, options)
.then((res) => res.data)
.catch((err) => Promise.reject(err?.response?.data?.message ?? "Error"));
};
react/components/Comment/CommentForm.js
import React from "react";
import { Form, Field } from "react-final-form";
import { usePost } from "../../contexts/PostContext";
import { createComment } from "../../services/comment";
import { useAsyncFn } from "../../hooks/useAsync";
const CommentForm = () => {
const { post, createLocalComment } = usePost();
const { loading, error, execute: createCommentFn } = useAsyncFn(
createComment
);
const onCommentCreate = (message) => {
return createCommentFn({ message, postId: post.id });
};
const handleSubmit = (values) => {
onCommentCreate(values);
};
return (
<Form onSubmit={handleSubmit}>
{({ handleSubmit }) => (
<form onSubmit={handleSubmit}>
<div className="comment-form-row">
<Field name="body">
{({ input }) => (
<textarea
className="comment-input"
placeholder="Your comment..."
type="text"
{...input}
/>
)}
</Field>
<button className="comment-submit-btn" type="submit">
Submit
</button>
</div>
</form>
)}
</Form>
);
};
export default CommentForm;

how to save react js state into localstorage

I have no idea How to store the react js state into localstorage.
import React, { Component } from 'react'
import './App.css';
import { auth,createUserProfileDocument } from './firebase/firebase.utils'
import { TodoForm } from './components/TodoForm/TodoForm.component'
import {TodoList} from './components/TodoList/TodoList.component'
import {Footer} from './components/footer/footer.component'
import Header from '../src/components/header/header.component'
import {Redirect} from 'react-router-dom'
import {connect} from 'react-redux'
import {setCurrentUser} from './redux/user/user.actions'
export class App extends Component {
constructor(props) {
super(props)
this.input=React.createRef()
this.state = {
todos:[
{id:0, content:'Welcome Sir!',isCompleted:null},
]
}
}
todoDelete = (id) =>{
const todos = this.state.todos.filter(todo => {
return todo.id !== id
})
this.setState({
todos
})
}
toDoComplete = (id,isCompleted) =>{
console.log(isCompleted)
var todos = [...this.state.todos];
var index = todos.findIndex(obj => obj.id === id);
todos[index].isCompleted = !isCompleted;
this.setState({todos});
console.log(isCompleted)
}
addTODO = (todo) =>{
todo.id = Math.random()
todo.isCompleted = true
let todos = [...this.state.todos, todo]
this.setState({
todos
})
}
unsubscribeFromAuth = null;
componentDidMount() {
const { setCurrentUser } = this.props;
this.unsubscribeFromAuth = auth.onAuthStateChanged(async userAuth => {
if (userAuth) {
const userRef = await createUserProfileDocument(userAuth);
userRef.onSnapshot(snapShot => {
setCurrentUser({
id: snapShot.id,
...snapShot.data()
});
});
}
setCurrentUser(userAuth);
});
}
componentWillUnmount() {
this.unsubscribeFromAuth();
}
render() {
return (
<div className='App'>
<Header />
<TodoForm addTODO={this.addTODO} />
<TodoList
todos={this.state.todos}
todoDelete={ this.todoDelete}
toDoComplete={ this.toDoComplete}
/>
<Footer/>
</div>
)
}
}
const mapStateToProps = ({ user }) => ({
currentUser: user.currentUser
});
const mapDispatchToProps = dispatch => ({
setCurrentUser: user => dispatch(setCurrentUser(user))
});
export default connect(
mapStateToProps,
mapDispatchToProps
)(App);
in my input Form
import './TodoForm.style.css'
export class TodoForm extends Component {
constructor(props) {
super(props)
this.state = {
content : ''
}
}
handleChange = (e) =>{
this.setState({
content: e.target.value
})
}
handleSubmit =(e) =>{
e.preventDefault();
this.props.addTODO(this.state);
this.setState({
content: ''
})
}
render() {
return (
<div className='inputTask'>
<form onSubmit={ this.handleSubmit}>
<input
className="textBox"
type='text'
onChange={ this.handleChange}
value={this.state.content}
placeholder='what you want to do ...'
/>
</form>
</div>
)
}
}
export default TodoForm
I have no idea How to store the react js state into localstorage.
i searched on internet but unable to find the exact solution all the codes that i think is necessary post.
You can use reactLocalStorage to save any data in local storage
import {reactLocalStorage} from 'reactjs-localstorage';
reactLocalStorage.set('var', true);
reactLocalStorage.get('var', true);
reactLocalStorage.setObject('var', {'test': 'test'});
reactLocalStorage.getObject('var');
reactLocalStorage.remove('var');
reactLocalStorage.clear();
Read out the localStorage item in the componentDidMount callback. Simply read the item you want to get, check if it exists and parse it to a usable object, array or datatype that need. Then set the state with the results gotten from the storage.
And to store it, simply handle it in an event handler or helper method to update both the state and the localStorage item.
class ExampleComponent extends Component {
constructor() {
super();
this.state = {
something: {
foo: 'bar'
}
}
}
componentDidMount() {
const storedState = localStorage.getItem('state');
if (storedState !== null) {
const parsedState = JSON.parse(storedState);
this.setState({ something: parsedState });
}
}
clickHandler = (event) => {
const value = event.target.value;
const stringifiedValue = JSON.stringify(value);
localStorage.setItem('state', stringifiedValue);
this.setState({ something: value });
}
render() {
return (
<button onClick={clickHandler} value={this.state.something}>Click me</button>
);
}
}
Set data in localStorage
key-value pair :
localStorage.setItem('key_name',"value");
object
localStorage.setItem('key_name', JSON.stringify(object));
Remove data from localStorage
localStorage.removeItem('key_name');
Get data from localStorage
let data = localStorage.getItem('key_name');
object :
let data = JSON.parse(localStorage.getItem('key_name'));
clear localStorage (delete all data)
localStorage.clear();

How do you implement a Higher-Order-Component in React?

I'm trying to set up a HOC in React to able to apply text selection detection to any Input component. However I seem to be missing something when I was trying to put it together.
I was following this article here on how to create a HOC:
https://levelup.gitconnected.com/understanding-react-higher-order-components-by-example-95e8c47c8006
My code (before the article looked like this):
import { func } from 'prop-types';
import React, { PureComponent } from 'react';
import { Input } from 'reactstrap';
class SelectableInput extends PureComponent {
handleMouseUp = () => {
const selection = window.getSelection();
if (selection) {
this.props.onSelectionChanged(selection.toString());
}
};
render() {
// eslint-disable-next-line
const { onSelectionChanged, ...rest } = this.props;
return <Input onMouseUp={this.handleMouseUp} {...rest} />;
}
}
SelectableInput.propTypes = {
onSelectionChanged: func
};
export default SelectableInput;
And I was using it like this:
render() {
return (
<SelectableInput
type="textarea"
name="textarea-input"
value={'This is some txt'}
onSelectionChanged={onTextSelectionChanged}
id="textarea-input"
onChange={e => this.onPageDataChanged(e)}
dir="rtl"
rows="14"
placeholder="Placeholder..."
/>
);
}
After reading the article I changed the above code to:
const SelectableInput = WrappedInput => {
class SelectableInputHOC extends PureComponent {
handleMouseUp = () => {
const selection = window.getSelection();
if (selection) {
this.props.onSelectionChanged(selection.toString());
}
};
render() {
// eslint-disable-next-line
const { onSelectionChanged, ...rest } = this.props;
return <WrappedInput onMouseUp={this.handleMouseUp} {...rest} />;
}
}
SelectableInputHOC.propTypes = {
onSelectionChanged: func
};
};
export default SelectableInput;
My question is how do I actually go about using it now in a render() function?
Thank you for your advance for your help.
SelectableInput is a function that returns a function that takes a component as a parameter and returns another component. You can use it like this:
const ResultComponent = ({...props}) =>
SelectableInput({...props})(YourParamComponent);
Then render ResultComponent wherever you want.
Here you have an example of using a HOC and passing props to it:
https://jsfiddle.net/58c7tmx2/
HTML:
<div id="root"></div>
JS
const YourParamComponent = ({ name }) => <div>Name: {name}</div>
const SelectableInput = ({...props}) =>
WrappedInput => <WrappedInput {...props} />
const ResultComponent = ({...props}) =>
SelectableInput({...props})(YourParamComponent);
const App = () => <ResultComponent name="textarea-input" />
ReactDOM.render(
<App />,
document.getElementById('root')
)

How do I manipulate data that is located in state and display to page?

I'm making three separate axios calls that each set the state with some data. Where do I do my data manipulation with the state data not to change the state but to display something else where?
For example out of the transactionItems state, I want to get all transactions for the current date. All transaction items have the date set automatically when its added to the database.
I'm having issues parsing the data because my setstate seems to update 3 times with all the axios calls.
There are other data manipulations I would like to be able to do as well but I feel like I'll hit another roadblock.
import React, { Component } from "react";
import axios from "axios";
import moment from "moment";
import TransactionSummary from "./TransactionSummary";
import BudgetSummary from "./BudgetSummary";
import DebtSummary from "./DebtSummary";
class DashboardTable extends Component {
constructor(props) {
super(props);
this.state = {
transactionItems: [],
budgetItems: [],
debtItems: [],
spentToday: ""
};
}
componentDidMount() {
this.getTransactionData();
this.getBudgetData();
this.getDebtData();
}
getTransactionData = () => {
axios.defaults.headers.common["Authorization"] = localStorage.getItem(
"jwtToken"
);
axios
.get("/api/transactions")
.then(res =>
this.setState({
transactionItems: res.data
})
)
.catch(err => console.log(err));
};
getBudgetData = () => {
axios.defaults.headers.common["Authorization"] = localStorage.getItem(
"jwtToken"
);
axios
.get("/api/budgets")
.then(res =>
this.setState({
budgetItems: res.data
})
)
.catch(err => console.log(err));
};
getDebtData = () => {
axios.defaults.headers.common["Authorization"] = localStorage.getItem(
"jwtToken"
);
axios
.get("/api/debts")
.then(res =>
this.setState({
debtItems: res.data
})
)
.catch(err => console.log(err));
};
render() {
return (
<div>
<div className="content">
<TransactionSummary transactionItems={this.state.transactionItems} />
<BudgetSummary budgetItems={this.state.budgetItems} />
<DebtSummary debtItems={this.state.debtItems} />
</div>
</div>
);
}
}
export default DashboardTable;
Here's DebtSummary component
import React from "react";
const DebtSummary = props => {
let sumOfDebtItems = props.debtItems.reduce((a, c) => {
return a + c["balance"];
}, 0);
return (
<div>
<p>Debt Summary</p>
{sumOfDebtItems}
</div>
);
};
export default DebtSummary;
Like Hemadri said, the easiest way to do this is to move the 3 axios calls into their respective component
You can also move the data manipulation into a separate method and call it in the render method. You can write as many of these as you need, they can all read from the same state variable
DebtSummary example:
import React from "react";
class DebtSummary extends React.Component {
constructor(props) {
super(props);
this.state = {
debtItems: []
}
}
getDebtData = () => {
axios.defaults.headers.common["Authorization"] = localStorage.getItem(
"jwtToken"
);
axios
.get("/api/debts")
.then(res =>
this.setState({
debtItems: res.data
})
)
.catch(err => console.log(err));
};
// Do some data manipulation, in the case computing the debt sum
sumOfDebtItems = () => {
return this.state.debtItems.reduce((a, c) => {
return a + c["balance"];
}, 0);
}
// Load the debt data once the component has mounted
componentDidMount() {
this.getDebtData()
}
render() {
return (
<div>
<p>Debt Summary</p>
{this.sumOfDebtItems()}
</div>
);
}
};
export default DebtSummary;

React js filter not working correctly it won't return items when deleting characters

Im trying to create a filter function based on the user input. The filter works fine but it won't return items when I delete characters. I know it has something to do with updating the state. I hope someone can help me out.
import React, {Component} from 'react';
import { BrowserRouter as Router, Route, Link } from "react-router-dom";
class Fetch extends Component {
constructor(){
super();
this.state = {
data: []
}
this.handleChange = this.handleChange.bind(this)
}
handleChange = (event) => {
console.log(event.target.value);
return this.setState({data: this.state.data.filter(data => data.title.toLowerCase().includes(event.target.value.toLowerCase()))})
}
async componentDidMount() {
try {
const response = await fetch(`https://jsonplaceholder.typicode.com/todos`);
const json = await response.json();
this.setState({ data: json });
}
catch (error) {
console.error(error)
}
}
render() {
return(
<div>
<p><Link to={`/`}>Link to homepage</Link></p>
<form>
<input type="text" onChange={this.handleChange}></input>
</form>
<ul>
{
this.state.data.map(data => (
<li key={data.id}>{data.id} => {data.title}</li>
))
}
</ul>
</div>
)
}
}
export default Fetch;
It is because you don't keep the initial data obtained from the HTTP request. Here is the problem:
Initially state: data = []
ComponentDidMount: data = ['abc', 'bcd', 'cdf']
Filter for keyword b: data = ['abc', 'bcd'] (as cdf does not contain the letter b)
Erase the filter (filter = '') but your data variable has the value data = ['abc', 'bcd'], so it can return at most 2 values.
your code looks fine, but your filter function is overwriting the state's data property. I suggest storing the full array in data (as you are right now) and store the filtered results in another property of the state, something like this:
import React, {Component} from 'react';
import { BrowserRouter as Router, Route, Link } from "react-router-dom";
class Fetch extends Component {
constructor(){
super();
this.state = {
data: [],
filtered: [] // This will store your filtered elements from data
}
this.handleChange = this.handleChange.bind(this)
}
handleChange = (event) => {
console.log(event.target.value);
// Filter the array stored in data, but never update it.
// Update filtered instead.
return this.setState({filtered: this.state.data.filter(data => data.title.toLowerCase().includes(event.target.value.toLowerCase()))})
}
async componentDidMount() {
try {
const response = await fetch(`https://jsonplaceholder.typicode.com/todos`);
const json = await response.json();
// Keep the original data here.
this.setState({ data: json });
}
catch (error) {
console.error(error)
}
}
render() {
return(
<div>
<p><Link to={`/`}>Link to homepage</Link></p>
<form>
<input type="text" onChange={this.handleChange}></input>
</form>
<ul>
{
this.state.filtered.map(data => (
<li key={data.id}>{data.id} => {data.title}</li>
))
}
</ul>
</div>
)
}
}
export default Fetch;
Remember that filter don't modify the original array,it always returns a new one.
You can use the following solution to solve your problem:
this.setState({ data: this.state.data.filter(data => data.title.toLowerCase().indexOf(event.target.value.toLowerCase().trim() !== -1) ) })

Categories

Resources