Responding to User Input lit-html - javascript

I am using lit-html and I can't seem to get the renderer to update, or any function to run on user input. I would like to check an input field on typing. I can't find any examples online showing responding to user input.
Here is the full code.
my_input.ts
import {html, directive, Part} from 'lit-html';
const stateMap = new WeakMap();
export const stateManager = directive(() => (part: Part) => {
let mystate: IInputData = stateMap.get(part);
if(mystate === undefined)
{
mystate = {
auto_id: Math.floor(Math.random() * 100000 + 1),
helper_eval: "",
input_message: "",
tab_index: 0,
input_value: "",
span_message: "",
}
stateMap.set(part, mystate);
}
else {
console.log("Hey");
part.setValue(mystate);
part.commit();
}
});
export interface IInputData {
auto_id: number,
helper_eval: string,
input_message: string,
tab_index: number,
input_value: string,
span_message: string
}
export let my_input = () => {
let d: IInputData = stateManager() as unknown as IInputData;
return html`
<section>
<label for="${d.auto_id}"
title="${d.helper_eval}">
${d.input_message} &#x24D8
<span class="float_left">
${d.span_message}
</span>
</label>
<input id="${d.auto_id}"
tabindex="${d.tab_index}" value="${d.input_value}">
</input>
<section>
`;
}
app.ts
const myTemplate = () => html`
<div>
${renderCounter(0)}
</div>`;
const renderCounter = directive((initialValue) => (part: any) => {
if(part.value === undefined)
{
part.setValue(initialValue);
}
else { part.setValue(part.value +1)}
}
);
export let app = (data: TemplateResult[]) => html`
${data}
`;
render(app([my_input(), myTemplate]), document.body);
I have included the render counting example, and don't see the render counter increase while typing in the input field. I am also not sure how I can add hooks to respond to the IInputData that change in that template. My best guess is to add some sort of callback function to the stateManager directive.

If you are looking for a basic usage of lit-html, please read on. If you are trying to figure out how to implement a custom directive, then I hope someone else can answer it for you. Also, I don't know Typescript so my answer below is in a plain JavaScript.
The idea behind lit-html is for you to call render() function every time you want to update the HTML. It does not self-update its output.
So, to use it with an input element, you need to implement a callback function to update template's dependent data-structure and to re-render. The callback must be attached to your template with #input/#change/#click etc.
Here is a simple example (loosely based on yours):
// create a data object containing the template variables
const data = {
span_message: "input text copy appears here"
}
// callback function for the input event
const inputCallback = (evt) => {
// update the template data
d.span_message = evt.target.value;
// re-render the template
render(myTemplate(data), document.body);
};
// function to generate a template
// d - data object, which controls the template
const myTemplate = (d) => html`
<input
#input=inputCallback
</input>
<span>
${d.span_message}
</span>`;
// initial rendering of the template
render(myTemplate(data), document.body);
Hope this helps

Related

React state updating in the dom but not in function

I have an onClick event on an input field called handleLinkChange that validates its content (a YouTube link) and if it's valid calls a function that extract its YouTube ids.
Within the onClick event I update the links state so the user sees if the link is valid and the extraction of ids is in progress (indicated by a loading wheel in the input field). At this point the state updates as desired in the DOM. However, when I arrive in the getIdsFromLink function after setting the state, the links state is still at the initial value. This makes it impossible to alter the links, for example to replace the loading indicator of the link with a checkmark when the ids have been parsed.
// Links state
const [links, setLinks] = useState([{ link: '', state: IS_VALID, errorMessage: '' }])
// onChange event handler
const handleLinkChange = (event, index) => {
const clonedLinks = [...links]
let value = decodeURI(event.target.value.trim())
const { passesRegex, validityMessage } = checkLinkValidity(value)
clonedLinks[index] = {
link: value,
state: passesRegex ? IS_VALIDATING_SERVER_SIDE : IS_ERROR,
errorMessage: validityMessage,
}
setLinks(clonedLinks)
if (clonedLinks[index] !== '' && passesRegex) {
getIdsFromLink(clonedLinks[index].link, index)
}
}
// Parser
const getIdsFromLink = (link, index) => {
console.log('links state', links) // initial state
socket.emit('getIds', link)
socket.on('idsParsed', newIds => {
console.log('links state after parse', links) // initial state
})
}
// Shortened return
return (
links.map((link, index) => (
<Input
value={link.link}
onChange={event => handleLinkChange(event, index)}
/>
{link.link && (
<FontAwesomeIcon
icon={link.state === IS_VALID ? faCheck : link.state === IS_ERROR ? faTimes : faSpinner}
/>
)}
)
))
I know that states are asynchronous and I also tried watching for state changes with useEffect, but I'm unable to refactor my code in that way and I have another state object that heavily depends on the links state, so I gave up on that one.
Even when I try to use flushSync to update the state synchronously, I have the same effect.
I very much appreciate your help! Thanks!
I'm gonna answer my question as I figured it out thanks to Nick in the comments.
Basically I kept the handleLinkChange function unchanged. But in the getIdsFromLink function, when the link has been parsed from the server - that's where I have to update the state - I used setLinks with a function as parameter. The function receives an argument which resembles the current state. All I had to do is to make a copy of the object (for the rerender to work), then make my changes to it and finally return the new object.
const getIdsFromLink = (link, index) => {
socket.emit('getIds', link)
socket.once('idsParsed', ids => {
// Function parameter to get current state
setLinks(_currentLinks => {
const currentLinks = [..._currentLinks] // copy to force rerender
currentLinks[index].state = IS_VALID
currentLinks[index].ids = ids
console.log('finished currentLinks', currentLinks) // newest state
return currentLinks
})
})
}

How to use getElementsByClassName on dynamic html

What I'm trying to do is to get HTML tag by className on dynamic HTML that I fetched,
but it returns undefined. It works if I try to getElementByClassName("main-page") because that class isn't dynamic
const HomePage = () => {
const [pageData, setPageData] = useContext(PageContext)
useEffect(() => {
const allImages = document.getElementsByClassName("wp-block-column")
console.log([...allImages])
}, [])
//render fronpage
const renderMainPage = () => {
//map the data and check for the site url (www.siteurl.com = front page)
if (pageData) {
return pageData.map(page => {
if (window.location.origin + "/" === page.link) {
return <section dangerouslySetInnerHTML={{ __html: page.content.rendered }}></section>
}
})
}
}
return (
<h1 className="main-page"">{renderMainPage()}</h1>
)
}
export default HomePage
From what I see the problem in pageData in context, probably your default value to context's pageData is false and renderMainPage() is not going further then first "if" statement
But its not preferred to use document selectors, use refs in React instead.
Also from naming I see that you are trying to get images and not dom nodes, but with this logic you are going to get dom nodes. I'm sure there is better way/flow to access images you need than looking for them in nodes.
const elementsRef = useRef(data.map(() => createRef()));
you can you dynamic create ref to access or create random iniNumber Array then assing to you elements when select them
You can use this function that useLayoutEffect, you can modify your code like this,
useLayoutEffect(()=> {
document.getElementsByClassName("yourclassname")
})

Draft js Editor gets slower as the content increases due to having many decorators

So my draft-js editor becomes really slow(hacky) the more content I insert (after about 20 decorator replacements). I am guessing this behavior is due to the decorator which checks the entire editor content using regex and replaces the matches with emoji component every time the state changes. I am also creating entities for each of the matches the regex find, I do this by decorating the component with editor state as a prop. Is there a way to make it faster?
Here is my decorator :
{
strategy: emojiStrategy,
component: decorateComponentWithProps(RenderEmoji, {
getEditorState: this.getEditorState,
setEditorState: this.onChange
})
}
here is my emojiStrategy :
function emojiRegexF(regex, contentBlock, callback, contentState) {
const text = contentBlock.getText();
let matchArr, start;
while ((matchArr = regex.exec(text)) !== null) {
start = matchArr.index;
callback(start, start + matchArr[0].length);
}
}
function emojiStrategy(contentBlock, callback, contentState) {
emojiRegexF(EMOJI_REGEX, contentBlock, callback, contentState);
}
here is my RenderEmoji component:
const RenderEmoji = props => {
const contentBlock = props.children[0].props.block;
const emojiKey = contentBlock.getEntityAt(props.children[0].props.start);
const emojiShortName = props.decoratedText;
if (!emojiKey) {
setEntity(props, emojiShortName);
}
return (
<Emoji emoji={emojiShortName} set="emojione" size={24}>
{props.children}
</Emoji>
);
};
and here is my setEntity function that sets the entity for the match:
function setEntity(props, emojiShortName) {
const editorState = props.getEditorState();
const contentstate = editorState.getCurrentContent();
const contentStateWithEntity = contentstate.createEntity(
"emoji",
"IMMUTABLE",
{
emojiUnicode: emojiShortName
}
);
const entityKey = contentStateWithEntity.getLastCreatedEntityKey();
const oldSelectionState = editorState.getSelection();
const selectionState = oldSelectionState.merge({
focusOffset: props.children[0].props.start + props.decoratedText.length,
anchorOffset: props.children[0].props.start
});
const newContentState = Modifier.applyEntity(
contentstate,
selectionState,
entityKey
);
const withBlank = Modifier.replaceText(
newContentState,
selectionState,
emojiShortName + " ",
null,
entityKey
);
const newEditorState = EditorState.push(
editorState,
withBlank,
"apply-entity"
);
props.setEditorState(newEditorState);
}
Any way I can optimize this? Thanks
I'm not sure if this would be the source of any real performance problem but there are two things that seem funny:
Matching the emoji decorator by regex, even though you're creating entities for them.
Changing the editor state (via setEntity) during the rendering of the decorator. Render functions should be pure.
I imagine you do this type of processing because emojis might be inserted via copy-paste, or via some kind of native emoji picker. A better way would be to:
Insert entities for emojis with the setEntity logic as part of onChange – before the content is saved and ultimately rendered.
Use a decorator strategy based on entities only, e.g.:
const emojiStrategy = (contentBlock, callback, contentState) => {
contentBlock.findEntityRanges(character => {
const entityKey = character.getEntity();
return (
entityKey !== null &&
contentState.getEntity(entityKey).getType() === 'emoji'
);
}, callback);
};
Then your decorator component won't need to update the editor state during the rendering. You also might not need to use decorateComponentWithProps anymore.
Now back to performance – the best way for you to know for sure how to improve it is to profile your app. You'll be able to tell exactly what takes time to render during keystrokes, and then track down the issue.

RxJS and React multiple clicked elements to form single data array

So I just started trying to learn rxjs and decided that I would implement it on a UI that I'm currently working on with React (I have time to do so, so I went for it). However, I'm still having a hard time wrapping my head around how it actually works... Not only "basic" stuff like when to actually use a Subject and when to use an Observable, or when to just use React's local state instead, but also how to chain methods and so on. That's all too broad though, so here's the specific problem I have.
Say I have a UI where there's a list of filters (buttons) that are all clickeable. Any time I click on one of them I want to, first of all, make sure that the actions that follow will debounce (as to avoid making network requests too soon and too often), then I want to make sure that if it's clicked (active), it will get pushed into an array and if it gets clicked again, it will leave the array. Now, this array should ultimately include all of the buttons (filters) that are currently clicked or selected.
Then, when the debounce time is done, I want to be able to use that array and send it via Ajax to my server and do some stuff with it.
import React, { Component } from 'react';
import * as Rx from 'rx';
export default class CategoryFilter extends Component {
constructor(props) {
super(props);
this.state = {
arr: []
}
this.click = new Rx.Subject();
this.click
.debounce(1000)
// .do(x => this.setState({
// arr: this.state.arr.push(x)
// }))
.subscribe(
click => this.search(click),
e => console.log(`error ---> ${e}`),
() => console.log('completed')
);
}
search(id) {
console.log('search --> ', id);
// this.props.onSearch({ search });
}
clickHandler(e) {
this.click.onNext(e.target.dataset.id);
}
render() {
return (
<section>
<ul>
{this.props.categoriesChildren.map(category => {
return (
<li
key={category._id}
data-id={category._id}
onClick={this.clickHandler.bind(this)}
>
{category.nombre}
</li>
);
})}
</ul>
</section>
);
}
}
I could easily go about this without RxJS and just check the array myself and use a small debounce and what not, but I chose to go this way because I actually want to try to understand it and then be able to use it on bigger scenarios. However, I must admit I'm way lost about the best approach. There are so many methods and different things involved with this (both the pattern and the library) and I'm just kind of stuck here.
Anyways, any and all help (as well as general comments about how to improve this code) are welcome. Thanks in advance!
---------------------------------UPDATE---------------------------------
I have implemented a part of Mark's suggestion into my code, but this still presents two problems:
1- I'm still not sure as to how to filter the results so that the array will only hold IDs for the buttons that are clicked (and active). So, in other words, these would be the actions:
Click a button once -> have its ID go into array
Click same button again (it could be immediately after the first
click or at any other time) -> remove its ID from array.
This has to work in order to actually send the array with the correct filters via ajax. Now, I'm not even sure that this is a possible operation with RxJS, but one can dream... (Also, I'm willing to bet that it is).
2- Perhaps this is an even bigger issue: how can I actually maintain this array while I'm on this view. I'm guessing I could use React's local state for this, just don't know how to do it with RxJS. Because as it currently is, the buffer returns only the button/s that has/have been clicked before the debounce time is over, which means that it "creates" a new array each time. This is clearly not the right behavior. It should always point to an existing array and filter and work with it.
Here's the current code:
import React, { Component } from 'react';
import * as Rx from 'rx';
export default class CategoryFilter extends Component {
constructor(props) {
super(props);
this.state = {
arr: []
}
this.click = new Rx.Subject();
this.click
.buffer(this.click.debounce(2000))
.subscribe(
click => console.log('click', click),
e => console.log(`error ---> ${e}`),
() => console.log('completed')
);
}
search(id) {
console.log('search --> ', id);
// this.props.onSearch({ search });
}
clickHandler(e) {
this.click.onNext(e.target.dataset.id);
}
render() {
return (
<section>
<ul>
{this.props.categoriesChildren.map(category => {
return (
<li
key={category._id}
data-id={category._id}
onClick={this.clickHandler.bind(this)}
>
{category.nombre}
</li>
);
})}
</ul>
</section>
);
}
}
Thanks, all, again!
Make your filter items an Observable streams of click events using Rx.Observable.fromevent (see https://github.com/Reactive-Extensions/RxJS/blob/master/doc/gettingstarted/events.md#converting-a-dom-event-to-a-rxjs-observable-sequence) - it understands a multi-element selector for the click handling.
You want to keep receiving click events until a debounce has been hit (user has enabled/disabled all filters she wants to use). You can use the Buffer operator for this with a closingSelector which needs to emit a value when to close the buffer and emit the buffered values.
But leaves the issue how to know the current actual state.
UPDATE
It seems to be far easier to use the .scan operator to create your filterState array and debounce these.
const sources = document.querySelectorAll('input[type=checkbox]');
const clicksStream = Rx.Observable.fromEvent(sources, 'click')
.map(evt => ({
name: evt.target.name,
enabled: evt.target.checked
}));
const filterStatesStream = clicksStream.scan((acc, curr) => {
acc[curr.name] = curr.enabled;
return acc
}, {})
.debounce(5 * 1000)
filterStatesStream.subscribe(currentFilterState => console.log('time to do something with the current filter state: ', currentFilterState);
(https://jsfiddle.net/crunchie84/n1x06016/6/)
Actually, your problem is about RxJS, not React itself. So it is easy. Suppose you have two function:
const removeTag = tagName =>
tags => {
const index = tags.indexOf(index)
if (index !== -1)
return tags
else
return tags.splice(index, 1, 0)
}
const addTag = tagName =>
tags => {
const index = tags.indexOf(index)
if (index !== -1)
return tags.push(tagName)
else
return tags
}
Then you can either using scan:
const modifyTags$ = new Subject()
modifyTags$.pipe(
scan((tags, action) => action(tags), [])
).subscribe(tags => sendRequest(tags))
modifyTags$.next(addTag('a'))
modifyTags$.next(addTag('b'))
modifyTags$.next(removeTag('a'))
Or having a separate object for tags:
const tags$ = new BehaviorSubject([])
const modifyTags$ = new Subject()
tags$.pipe(
switchMap(
tags => modifyTags$.pipe(
map(action => action(tags))
)
)
).subscribe(tags$)
tags$.subscribe(tags => sendRequest(tags))

How to fast render >10000 items using React + Flux?

I would like to ask what is the correct way to fast render > 10000 items in React.
Suppose I want to make a checkboxList which contain over dynamic 10000 checkbox items.
I make a store which contain all the items and it will be used as state of checkbox list.
When I click on any checkbox item, it will update the corresponding item by action and so the store is changed.
Since store is changed so it trigger the checkbox list update.
The checkbox list update its state and render again.
The problem here is if I click on any checkbox item, I have to wait > 3 seconds to see the checkbox is ticked. I don't expect this as only 1 checkbox item need to be re-rendered.
I try to find the root cause. The most time-consuming part is inside the checkbox list render method, related to .map which create the Checkbox component to form componentList.. But actually only 1 checkbox have to re-render.
The following is my codes.
I use ReFlux for the flux architecture.
CheckboxListStore
The Store store all the checkbox item as map. (name as key, state (true/false) as value)
const Reflux = require('reflux');
const Immutable = require('immutable');
const checkboxListAction = require('./CheckboxListAction');
let storage = Immutable.OrderedMap();
const CheckboxListStore = Reflux.createStore({
listenables: checkboxListAction,
onCreate: function (name) {
if (!storage.has(name)) {
storage = storage.set(name, false);
this.trigger(storage);
}
},
onCheck: function (name) {
if (storage.has(name)) {
storage = storage.set(name, true);
this.trigger(storage);
}
},
onUncheck: function (name) {
if (storage.has(name)) {
storage = storage.set(name, false);
this.trigger(storage);
}
},
getStorage: function () {
return storage;
}
});
module.exports = CheckboxListStore;
CheckboxListAction
The action, create, check and uncheck any checkbox item with name provided.
const Reflux = require('reflux');
const CheckboxListAction = Reflux.createActions([
'create',
'check',
'uncheck'
]);
module.exports = CheckboxListAction;
CheckboxList
const React = require('react');
const Reflux = require('reflux');
const $ = require('jquery');
const CheckboxItem = require('./CheckboxItem');
const checkboxListAction = require('./CheckboxListAction');
const checkboxListStore = require('./CheckboxListStore');
const CheckboxList = React.createClass({
mixins: [Reflux.listenTo(checkboxListStore, 'onStoreChange')],
getInitialState: function () {
return {
storage: checkboxListStore.getStorage()
};
},
render: function () {
const {storage} = this.state;
const LiComponents = storage.map((state, name) => {
return (
<li key = {name}>
<CheckboxItem name = {name} />
</li>
);
}).toArray();
return (
<div className = 'checkbox-list'>
<div>
CheckBox List
</div>
<ul>
{LiComponents}
</ul>
</div>
);
},
onStoreChange: function (storage) {
this.setState({storage: storage});
}
});
module.exports = CheckboxList;
CheckboxItem
Inside onChange callback, I call the action to update the item.
const React = require('react');
const Reflux = require('reflux');
const $ = require('jquery');
const checkboxListAction = require('./CheckboxListAction');
const checkboxListStore = require('./CheckboxListStore');
const CheckboxItem = React.createClass({
mixins: [Reflux.listenTo(checkboxListStore, 'onStoreChange')],
propTypes: {
name: React.PropTypes.string.isRequired
},
getInitialState: function () {
const {name} = this.props;
return {
checked: checkboxListStore.getStorage().get(name)
};
},
onStoreChange: function (storage) {
const {name} = this.props;
this.setState({
checked: storage.get(name)
});
},
render: function () {
const {name} = this.props;
const {checked} = this.state;
return (
<div className = 'checkbox' style = {{background: checked ? 'green' : 'white'}} >
<span>{name}</span>
<input ref = 'checkboxElement' type = 'checkbox'
onChange = {this.handleChange}
checked = {checked}/>
</div>
);
},
handleChange: function () {
const {name} = this.props;
const checked = $(this.refs.checkboxElement).is(':checked');
if (checked) {
checkboxListAction.check(name);
} else {
checkboxListAction.uncheck(name);
}
}
});
module.exports = CheckboxItem;
There are a few approaches you can take:
Don't render all 10,000 - just render the visible check boxes (+ a few more) based on panel size and scroll position, and handle scroll events to update the visible subset (use component state for this, rather than flux). You'll need to handle the scroll bar in some way, either by rendering one manually, or easier by using the normal browser scroll bar by adding huge empty divs at the top and bottom to replace the checkboxes you aren't rendering, so that the scroll bar sits at the correct position. This approach allows you to handle 100,000 checkboxes or even a million, and the first render is fast as well as updates. Probably the preferred solution. There are lots of examples of this kind of approach here: http://react.rocks/tag/InfiniteScroll
Micro-optimize - you could do storage.toArray().map(...) (so that you aren't creating an intermediate map), or even better, make and empty array and then do storage.forEach(...) adding the elements with push - much faster. But the React diffing algorithm is still going to have to diff 10000 elements, which is never going to be fast, however fast you make the code that generates the elements.
Split your huge Map into chunks in some way, so that only 1 chunk changes when you check a chechbox. Also split up the React components in the same way (into CheckboxListChunks) or similar. This way, you'll only need to re-render the changed chunk, as long as you have a PureComponent type componentShouldUpdate function for each chunk (possibly Reflux does this for you?).
Move away from ImmutableJS-based flux, so you have better control over what changes when (e.g. you don't have to update the parent checkbox map just because one of the children has changed).
Add a custom shouldComponentUpdate to CheckboxList:
shouldComponentUpdate:function(nextProps, nextState) {
var storage = this.state.storage;
var nextStorage = nextState.storage;
if (storage.size !== nextStorage.size) return true;
// check item names match for each index:
return !storage.keySeq().equals(nextStorage.keySeq());
}
Beyond the initial render, you can significantly increase rendering speed of large collections by using Mobservable. It avoids re-rendering the parent component that maps over the 10.000 items unnecessarily when a child changes by automatically applying side-ways loading. See this blog for an in-depth explanation.
Btw, I give up flux...
I finally decided to use mobservable to solve my problem.
I have made an example https://github.com/raymondsze/react-example
see the https://github.com/raymondsze/react-example/tree/master/src/mobservable for the coding.
Your render function looks somewhat more complicated then it needs to be:
it first generates an array of JSX components
then converts applies a (jQuery?) .toArray()
then returns this newly generated array.
Maybe simplifying your render function to something like this would help?
render: function () {
return (
<div className = 'checkbox-list'>
<div>
CheckBox List
</div>
<ul>
{this.state.storage.map((state, name) => {
return (
<li key = {name}>
<CheckboxItem name = {name} />
</li>
);
})}
</ul>
</div>
);
},
Do you really need to save the check status in you store every time check/uncheck?
I recently meet similar problem like you. Just save a checkedList array [name1,name2 ...] in the CheckboxList component's state, and change this checkedList every time you check/uncheck an item. When you want to save check status to data storage, call an Action.save() and pass the checkedList to store.
But if you really need to save to data storage every time check/uncheck, this solution won't help -_-.

Categories

Resources