Searching huge object by property performant caching - javascript

i have a huge js object which hold all user posts,
some users might have more than 300+ post in this object, so i want to implement a search mechanism
example:
{
postById:{
1:{ id:1, title:'varchar 250 string' },
2:{ id:2, title:'varchar 250 string' },
...etc for 300+ items
}
}
ps.: i' using ES6, its a react-native project.
a simple approach for search can be:
_handlSearch(keyword){
const key= keyword.toLowerCase();
return Object.keys(postById).filter(id=>posts[id].title.indexOf(key)>-1)
.map(id=>postById[id])
}
now this function is fine, by question is how often should i trigger search ?
and lets say user type "var" this will trigger search, then he add a new letter "varc" so it would make sence to not filter the master object buy rather search the already short list which came from "var".
is there a already existent solution that optimize such autocomplete/search functionality ?

Updated.
You can set master list as list state property and then repeatedly search in it until you don't have some keyword which doesn't contain your current keyword in component.
Basically, this version:
import React, { Component } from 'react';
class ShortList extends Component {
constructor(props){
super(props);
this.state = {list:props.master,keyword:String(props.keyword||'').toLowerCase()};
this._hadleSearchMaster = this._hadleSearchMaster.bind(this)
this.trigger = this.trigger.bind(this)
this.extract = typeof props.getValue==='function' ? props.getValue : f=>String(f);
this.minLength = props.minLength||3;
this.debounce = 0;
}
_hadleSearchMaster(){
const list = Object.keys(this.props.master).map(id=>this.extract(this.props.master[id]).indexOf(this.state.keyword)>-1);
console.log('searched master and returned'+this.state.keyword,Object.keys(this.props.master),list);
this.setState({list});
}
trigger(){
clearTimeout(this.debounce);
this.debounce = setTimeout(this._hadleSearchMaster, 200);
}
componentWillReceiveProps(props){
this.extract = typeof props.getValue==='function' ? props.getValue : f=>String(f);
if(props.getValue!==this.props.getValue)this.extract = props.getValue;
if(props.minLength!==this.props.minLength)this.minLength = props.getValue;
if(!props.keyword || props.keyword.length < this.minLength)return;
if(props.keyword===this.props.keyword)return;
const keyword = String(props.keyword||'').toLowerCase();
const stateToSet = {keyword};
if (keyword.substr(0, this.state.keyword.length) !== this.state.keyword) {
stateToSet.list = props.master;
}
this.setState(stateToSet,
this.trigger)
}
render() {
return this.props.render(this.state.list);
}
}
//<Shortlist master={{}} style={{}} getValue={f=>f.title.toLowerCase()} keyword='search' render={idList=>null} minLength={3} />
export default ShortList

Related

updating props when using createControlComponent in react-leaflet 3

Following the offical reference for Higher Level Component Factory to update props for a Control Component
The core APIs export other high-level component factories that can be
used in a similar way.
I've mimicked the example - but I get a syntax error for the following:
import L from "leaflet";
import "leaflet-routing-machine";
import { createControlComponent } from "#react-leaflet/core";
import 'leaflet-routing-machine/dist/leaflet-routing-machine.css'
function setWaypoints(props)
{
return {
waypoints: [
L.latLng(props.startLat, props.startLng),
L.latLng(props.endLat, props.endLng)
],
lineOptions: {
styles: [{ color: "#0500EE", weight: 4 }]
},
show: false,
addWaypoints: false,
routeWhileDragging: true,
draggableWaypoints: true,
fitSelectedRoutes: true,
showAlternatives: false,
createMarker: function() { return null; },
}
}
function createRoutingMachine(props, context)
{
const instance = new L.Routing.control(setWaypoints(props))
return
{
instance, context: { ...context, overlayContainer: instance }
}
}
function updateRoutingMachine(instance, props, prevProps)
{
if (props.endLat !== prevProps.endLat || props.endLng !== prevProps.endLng)
{
instance.setWaypoints(props)
}
}
const RoutingMachine = createControlComponent(createRoutingMachine, updateRoutingMachine)
export default RoutingMachine;
Missing semicolon. (35:25)
33 | return 34 | {
35 | instance, context: { ...context, overlayContainer: instance }
| ^ 36 | }
If I change this to:
function createRoutingMachine(props)
{
const instance = new L.Routing.control(setWaypoints(props))
return instance
}
The compiler is happy, but the component never updates.
I know I'm creating the Control Component incorrectly, but I can't find the information for the correct implementation.
Related:
How to use Leaflet Routing Machine with React-Leaflet 3?
How to extend TileLayer component in react-leaflet v3?
You will notice that in the docs, createcontrolcomponent lists only one argument, which is a function to create the instance. You are expecting it to behave like createlayercomponent, which takes two arguments. In createlayercomponent, the second argument is a function to update the layer component when the props change. However, createcontrolcomponent offers no such functionality. react-leaflet is assuming, much like vanilla leaflet, that once your control is added to the map, you won't need to alter it directly.
This gets a bit confusing in terms of leaflet-routing-machine, because you don't need to change the instance of the control, but rather you need to call a method on it which affects the map presentation.
IMO, the best way to go is to use a state variable to keep track of whether or not your waypoints have changed, and use a ref to access the underlying leaflet instance of the routing machine, and call setWayPoints on that:
// RoutineMachine.jsx
const createRoutineMachineLayer = (props) => {
const { waypoints } = props;
const instance = L.Routing.control({
waypoints,
...otherOptions
});
return instance;
};
// Takes only 1 argument:
const RoutingMachine = createControlComponent(createRoutineMachineLayer);
// Map.jsx
const Map = (props) => {
// create a ref
const rMachine = useRef();
// create some state variable, any state variable, to track changes
const [points, setPoints] = useState(true);
const pointsToUse = points ? points1 : points2;
// useEffect which responds to changes in waypoints state variable
useEffect(() => {
if (rMachine.current) {
rMachine.current.setWaypoints(pointsToUse);
}
}, [pointsToUse, rMachine]);
return (
<MapContainer {...props}>
<RoutineMachine ref={rMachine} waypoints={pointsToUse} />
<button onClick={() => setPoints(!points)}>
Toggle Points State and Props
</button>
</MapContainer>
);
};
Working Codesandbox
Bonus: a cheap and easy way to force a rerender on your <RoutineMachine> component (or any react component) is to assign it a key prop, and change that key prop when you want to rerender it. This might be a uuid, or even a unique set of waypoints ran through JSON.stringify. Just an idea.

Is it possible to hold a list of react components in state and render them?

I've been attempting to learn some React programming recently, and I ran into some confusion when learning about rendering lists.
The React documentation describes this method for rendering lists:
const listItems = numbers.map((number) =>
<li>{number}</li>
);
return (
<ul>{listItems}</ul>
);
Out of curiosity, I decided to try holding a list of React elements in the state of one of my components and render that list directly. I figured that it might be able to avoid the need of mapping the data to an element every time a render occurred.
I was able to make this work:
'use strict';
class List2 extends React.Component {
constructor(props) {
super(props);
let nums = [];
for(let i = 0; i < 5; i++) {
nums.push(React.createElement('div', null, i));
}
this.state = {
numbers : nums,
}
}
render() {
return (
React.createElement('div', null, this.state.numbers)
)
}
}
However, I tried to add a button to my window that added elements to the element list, the new elements added by the button's onCLick function don't render. This is the code that doesn't work:
'use strict';
class List3 extends React.Component {
constructor(props) {
super(props);
this.state = {
nextNum : 1,
numbers : [],
}
this.buttonAction = this.buttonAction.bind(this);
}
buttonAction() {
let numElement = React.createElement('h1', null, this.state.nextNum);
let newNumber = this.state.numbers;
newNumber.push(numElement);
this.setState(
{ nextNum : (state.nextNum + 1),
numbers : newNumbers,
}
);
}
render() {
return (
React.createElement('div', null,
this.state.numbers,
React.createElement('button', {onClick : this.buttonAction}, 'clicketh')
)
)
}
}
When I click the button, I don't see new numbers render on the screen.
Can someone help me explain what's going on here?
Is the mapping method from above the only reliable way to render lists with react?
React only renders those elements which got change from last render, If you want to force render then you have to force react to do it otherwise it will not render new elements.
Arrays are reference type so if will add or remove elements to it, it will not create a new copy and react will consider it as unchanged.
For your render issue you need to create a new copy of "numbers" each time you add element so react will consider it as changed state and render as new.
you can achieve this by using map function in your render method which will provide react a new copy of array or use "slice" in your button click event so while setting new numbers state it will create a new shallow copy of "numbers" each time.
below are snippets for doing it in both ways.
buttonAction() {
let numElement = React.createElement('h1', { key: this.state.nextNum },
this.state.nextNum);
let newNumbers = this.state.numbers;
newNumbers.push(numElement);
this.setState(
{
nextNum: (this.state.nextNum + 1),
numbers: newNumbers.slice(),
}
);
}
Or
render() {
return (
React.createElement('div', null,
this.state.numbers.map(item=>item),
React.createElement('button', { onClick: this.buttonAction }, 'clicketh')
)
)
}
for more info on array please follow below link.
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array
Really not sure why you are doing this.
But maybe you can try something like this ?
this.setState({
numbers :[...this.state.numbers, numElement],
});
Creating a copy of numbers instead of reusing the old reference.

ReactJS - sorting - TypeError: 0 is read only

I'm trying to sort an object before mapping it in reactjs, but every single time I do, I keep getting the TypeError: 0 is read only. I noticed that on load the props are empty, but even when I was trying to check the length of the array and only apply sort when it's higher than 0, or when the array exists to be precised, it would still happen. The moment I try to sort anything, it just stops working.
I would like to be able to sort these via different criteria in the future, but so far I can't even go through regular sorting on render...
I have the below:
ToysList.jsx
import React, { Component } from 'react';
import PropTypes from 'prop-types';
class ToysList extends Component {
constructor (props) {
super(props);
this.state = {};
}
render () {
var toys = this.props.toys;
var arr = toys.sort((a,b) => parseFloat(a.price) - parseFloat(b.price));
return (
<div>
<div key="list">
{ arr.map((toy, index) => (
<div key={toy.id}>
{toy.name}:<br/>
Quantity: {toy.quantity}<br/>
Price: {toy.price}<br/>
Rating: {toy.rating}
</div>
)) }
</div>
</div>
);
}
}
ToysList.propTypes = {
toys: PropTypes.arrayOf(
PropTypes.shape({
id: PropTypes.string,
name: PropTypes.string,
quantity:PropTypes.number,
price:PropTypes.number,
rating:PropTypes.number,
}),
).isRequired,
};
export default ToysList;
I'd be grateful for any hint with these.
Thanks
That could be because sort mutates the array it works on (it sorts in-place).
If this.props.toys is immutable then it would throw that error.
Create a copy of the array and sort that.
var toys = this.props.toys.slice();
// var toys = Array.from(this.props.toys);
// var toys = [...this.props.toys];
var arr = toys.sort((a,b) => parseFloat(a.price) - parseFloat(b.price));

unintentional state update in react form

First of all this is a simplified example: Codepen Project
I am building an edit form in react, which checks if there are any changes.
You can only save the form if there are any changes and any changes you made will be shown by changing the style (border-left) on the matching input element. It looks like this
To do that I am saving the original data/ state in the component state in the componentDidMount method and compare that to the state of the different inputs.
componentDidMount() {
// if the project is accessed from home and is not a new project, project data will be passed along
if (this.props.project) {
this.setState({
name: this.props.project.name,
tags: this.props.project.tags
}, this.setInitialState)
} else if (this.props.edit && this.props.match.params.id) {
// instead of an api call to get project data, if the project is accessed directly by url
const project = projects.find((project) => project.name === this.props.match.params.id)
this.setState({
name: project.name,
tags: project.tags
}, this.setInitialState)
}
// if there are no project data or an edit prop, it's a new project and the initialState remains empty
}
On each Input Change the input Value is compared to the initialState:
compareInputData() {
const formFields = {
name: {
ref : this.name,
changed : false
},
tags: {
ref : this.tagList,
changed : false
}
}
const state = this.state
const first = this.state.initialState
const nameHasChanged = state.name !== first.name
const tagsHaveChanged = state.tags.length !== first.tags.length
nameHasChanged
? ( formFields.name.changed = true )
: ( formFields.name.changed = false )
tagsHaveChanged
? ( formFields.tags.changed = true )
: ( formFields.tags.changed = false )
nameHasChanged || tagsHaveChanged
? (this.setState({
isChanged: true
}))
: (this.setState({
isChanged: false
}))
this.handleChangedInputStyles(formFields)
}
If there are changes the styling of the matching element is changed:
handleChangedInputStyles(formFields) {
const formFieldKeys = Object.keys(formFields)
formFieldKeys.map(key => {
formFields[key].changed
? formFields[key].ref.style.borderLeft = `2px solid orange`
: formFields[key].ref.style.borderLeft = '1px solid black'
})
}
That is working the way I want it to on normal input fields, but I am also saving related tags as an array, which are displayed as a list. Whenever I update that list (this.state.tags) my original state for the tags is being updated as well (this.state.initialState.tags), which means that I cannot pick up changes in my tag List.
However it does work if I am creating adding a tag to a new project instead of editing an existing one...
I have no idea how to fix that issue, since I don't really know what's causing it and I would love some help.
Thank you for reading through this post :)
Do not store this.state.initialState in the state. Store it in a member instead. For instance:
constructor(props) {
this.initialState = Object.assign({}, whatever...);
this.initialState.tags = [].concat(this.initialState.tags); // Keep a shallow copy of this array.
}
Note: Internally, React may modify the tags array. If you keep a copy, that copy will not be modified.

Conditional Rendering in Meteor + React

I'm trying to get some conditional rendering in Meter + React. I only want a component to show up if the number of items returned from a collection === 0.
I tried this:
renderInputForm () {
if (Tokens.find().count()) return;
return (<TokenForm />);
}
and put {this.renderInputForm()} in the main render(), but then it flashes for a split second before hiding it…
I know why the flash is happening but trying to avoid it ….
You must wait for the data to finish with synchronization. The flash is there because initially MiniMongo collections are empty. (Also, you might want to avoid Collection.find() in your render function.)
Assuming you use Meteor 1.3.x:
export const MyComponent = createContainer(() => {
let subscription = Meteor.subscribe('something');
if (!subscription.ready()) return {};
return {
tokens: Tokens.find().fetch()
}
}, InternalComponent);
And then check for the existence of props.tokens in your React component.
class InternalComponent extends React.Component {
render() {
if (!this.props.tokens || this.props.tokens.length > 0) return;
return <TokenForm />;
}
}
Find out more about subscriptions here: http://docs.meteor.com/#/full/meteor_subscribe

Categories

Resources