StencilJS: How to prevent "flickering" when loading component in a page - javascript

A group of volunteers has created a single/multi-select component using StencilJS: https://github.com/NothingAG/adg-components
Now we want to hand it over to the client so they can use it in their project. But we notice that loading the component seems to take some time after the page itself loaded, so some flickering appears (when the loaded component claims its horizontal space):
Is this normal behaviour of such a component? Or how can we allocate the needed space when the browser is rendering? We could try to simply use min-height or something with a fixed value, but this feels like a hack and may change over time.
Thanks a lot for help.

This is normal. A Stencil web component will go through its load lifecycle asynchronously after being loaded into DOM, and therefore it may not complete until after the DOM has completed loading and the page is rendered. If the component has to make a call to fetch data for example before it fully renders, that might take long enough for this "flicker" to be noticeable.
Strategies for dealing with this type of situation will depend on the component, but in your case it looks like one approach would be to render the labels and controls without selection items and maybe disabled so that the initial render can occur early, and trigger a component update via a State variable when fetched data becomes available which simply populates the selection controls, and possibly enables them.
So - and I'm making guesses about how your component works - rather than conditionally render the controls, conditionally populate the controls.
Crude example of rendering a select element empty until data is set on the component via a property (code not validated):
private _selectionItems = [];
#Prop() selectionItems: string = ''; // csv list of select options
#Watch('selectionItems')
setSelectionItems(selectionItems: string) {
selectionItems = selectionsItems || ''; // null check
this._selectionItems = selectionItems.split(',');
}
render() {
return (
<label for="my-select">Choose an item:</label>
<select id="my-select">
{this._selectionItems.map(
item: string => <option value={item}>{item}</option>
)}
</select>
);
}
Contrast with not rendering at all until data is set (showing render function only):
render() {
if (this._selectionItems.length > 0) {
return (
<label for="my-select">Choose an item:</label>
<select id="my-select">
{this._selectionItems.map(
item: string => <option value={item}>{item}</option>
)}
</select>
);
}
else {
return null;
}
}

Related

How to manage state in Edit Modal Form with Submit Function in React?

I appear to be going around in circles on this one.
I have created an Edit Form that is featured within a modal. This is accessed via a button that sits on a display page. The contents of the display page is dictated by a user menu. The display page component passes the object being displayed to the Edit model to be edited.
Please note that I am yet to tie the app into the backend so the form submission handle lacks any PUT call or error handling. Instead I am emulating this via a console.log.
I have included a portion of my Edit Form code as there are many fields.
import {react,useState,useEffect} from 'react'
import './EditPreGameModel.css'
import DropSearch from './DropSearch/DropSearch'
export default function EditPreGameModal( { open, onClose, preGameTrade} ) {
const [PreGame, setPreGame] = useState(preGameTrade)
const handleChange=(e)=> {
setPreGame({...PreGame,[e.target.name]:e.target.value})
}
const submitHandler=(e)=>{
e.preventDefault();
console.log(PreGame)
onClose()
}
if(!open) return null
return (
<div className='Overlay_style'></div>
<div className='EditPreGameModalCont'>
<div className='EditPreGameHeaders'>
<header className='PreGameName'>{preGameTrade.Tradename}</header>
<header className='editHeader'>PreGame Edit</header>
</div>
<form className='preGameForm' onSubmit={(e)=>submitHandler(e)}>
<div className='FirstEditItems'>
<label>Status:</label>
<select className="editDropdown" name="Status" value={PreGame.Status} onChange={(e)=>handleChange(e)}>
<option className="DropOptions" value="PreGame">PreGame</option>
<option className="DropOptions" value="InGame">InGame</option>
</select>
<label>Instrument:</label>
<select className="editDropdown" name="Instrument" value={PreGame.Instrument} onChange={(e)=>handleChange(e)}>
<option className="DropOptions" value="FX">FX</option>
<option className="DropOptions" value="Crypto">Crypto</option>
<option className="DropOptions" value="Equities">Equities</option>
</select>
<label>Ticker:</label>
<DropSearch ticker={PreGame.Instrument="FX"? PreGame.FXSymbol : PreGame.Instrument="Crypto"? PreGame.Crypto : PreGame.StockSymbol}></DropSearch>
</div>
<div className='SecondEditItems'>
<label>Category:</label>
<select className="editDropdown" name="BuySell" value={PreGame.BuySell} onChange={(e)=>handleChange(e)}>
<option className="DropOptions" value="Buy">Buy</option>
<option className="DropOptions" value="Sell">Sell</option>
</select>
</div>
<div className='EditControl'>
<button type='submit' className='EditControlButton'>Submit</button>
<button className='EditControlButton' onClick={onClose}>Exit</button>
</div>
</form>
</div>
)
}
The challenge I am facing is begins with the copy I am making via:
const [PreGame, setPreGame] = useState(preGameTrade)
This receives the object to be edited and creates a copy to be edited by the user prior to being submitted. This is done to stop edits occurring directly on the original. The logic here was that I didn't want edits to be passed back up to display page without a submission. This stops edits showing even when the user cancels the edit halfway through. The ambition was to have the user build the copy PreGame and commit that to a PUT request. The display component would then request a refresh of the data and present it back to the User.
However, this results in another issue. Selecting and Editing the first item seems fine but if the user navigates the menu, selects another object and clicks Edit, the object held within PreGame remains to be the original rather than the new. The fields remain tied to the original.
One final note - the Edit Form header is the name of the object and this isn't intended to be editable in this version. As a result I just call the name of the original object. This changes fine whilst navigating through different objects.
Am I approaching this incorrectly? Do I need a useEffect() ref to listen for changes in preGameTrade and then call the setPreGame()?
Thanks, any help is great appreciated.
To address the issue wherein your edit-form is not being sensitive to user selection.
you can add a useEffect block sensitive to preGameTrade prop
const [PreGame, setPreGame] = useState(preGameTrade)
useEffect(() => {
setPreGame(preGameTrade);
}, [preGameTrade]);
Explanation:
the state variable will not get updated on changing the props passed to the component by itself. To do this we need a useEffect block.
You need to update the object that's passed into props. If you want to edit the now modified instance again, you'll need to call a function that's passed into the editing component. We can call it the "update function".
That update function can be called, in your case, inside the submitHandler function - after the success of the PUT request. The update function is called in the child component, but it's execution happens on the parent.
This is called "lifting state".
Here's some snippets that I think capture the idea.
// The child component
const Child = (preGame, updatePregame) => {
const submitHandler=(e)=>{
e.preventDefault();
console.log(PreGame)
updatePregame(PreGame)
onClose()
}
};
// The parent component
const Parent = () => {
const updatePreGame = (pregame) => {
// Update the instance of pregame with the values
// from the copy modified in Child.
};
return (
<Child preGame={pregame} updatePregame={updatePregame} />
);
}
The issue is in the way you are adding properties to the object
setPreGame({...PreGame,`${e.target.name}`:e.target.value})
} ```

How to render a specific object within an array, when clicking on a component, in React?

Codesandbox link.
Okay, so the high level functionality of what I want this app to do:
Upon load, you're presented with the characters on the left, and on the right an intro screen.
When a user clicks on the first character (Cloud) for example, then the intro screen is replaced with Cloud's stats. If a user clicks on the second, third, etc character, same thing. The intro screen on the right is replaced with that character's specific stats.
That's basically it. I'm finding it really tough to do, for some reason.
For the /components/ folder breakdown (I'll go over the important ones):
/components/content/ - this is where the characters information lives, housed in the State. Each character is uniquely identified by an 'id'.
/components/sidebar/ - this is the sidebar component
/components/card-overview/ - this is a single component, repeated a few times, that houses the 'overview' of each character (image, character name), that you see in the sidebar itself
/components/card-detailed/ - this is where it grabs the array of objects from /components/content/, filters through and maps the character you're currently seeing to the DOM. Problem is, on line 13 of this component, you'll see I've hard-coded in the id #. Obviously, I want this to change to whatever character the user clicks, it then loads THAT character's info to the DOM. I just don't know how to do that.
In short, what I'm trying to do:
When a character on the left is clicked, hide whatever is on the right and replace it with that character's information.
I made your intention working here: https://codesandbox.io/s/l2lqm76z0q
As Chris pointed out, the way to go is using react state.
Therefore, I introduced a state with the default selected item 4 and created a handleClick function that should fire once you click on one item in your sidebar. Both of them got created in App.js:
state = {
selected: 4
};
handleClick = id => {
this.setState({ selected: id });
};
Now, I transferred the handleClick function where it is needed, in this case in Sidebar.js:
<Sidebar onClick={this.handleClick} />
And the currently selected item needs to be transferred to your content (again, where it is needed).
<Content selected={this.state.selected} />
In the sidebar, I send the handleClick function again to the it's child, CardOverview, with the related id(*):
<CardOverview
image={cloudImage}
name="Cloud Strife"
onClick={() => this.props.onClick(1)}
/>
And in CardOverview, finally, we use that function we passed down the road:
<section className="card-overview" onClick={this.props.onClick}>
...
</section>
(Whether it is working or not, we see in the console - when clicking on on item, it should show the state with the currently selected item id.)
Now, in content.js, we use the selected value and pass it down to CardDetailed where we use it in the following way:
// Grab the 'characters' object from App.js, and assign it to 'this.props'
const { characters, selected } = this.props;
// Filter the chracters and return only whose 'id' belongs to that of the selected one
const filteredCharacters = characters
.filter(character => character.id === selected)
.map(character => (
<div className="characters" key={character.id}>
<p>Name: {character.Name}</p>
<p>Job: {character.Job}</p>
<p>Age: {character.Age}</p>
<p>Weapon: {character.Weapon}</p>
<p>Height: {character.Height}</p>
<p>Birthdate: {character.Birthdate}</p>
<p>Birthplace: {character.Birthplace}</p>
<p>Bloodtype: {character.Bloodtype}</p>
<p>Description: {character.Description}</p>
</div>
));
...
That`s it.
(*) Be aware, I did not improve your code, I just made it working. I see several issues that you should tackle. E.g. it is not needed to list all the characters in your sidebar manually. I'd suggest to create a file that contains your list of characters and use it in sidebar and in content. This way, you will reduce your code and maintain your characters list (and all belonging information like id) in one file.

Semantic UI React, Dropdown Selection

Please take a look at my code for Dropdown,
I'm using the semantic ui react dropdown on an EditProfile component. I have pasted a sample code here, https://codesandbox.io/s/m4288nx4z8, but I could not get it to work because I'm not very familiar with functional components in React, I've always used Class component. But you can check the full code for the whole component below in the github gist.
https://gist.github.com/mayordwells/b0cbb7b63af85269091f1f98296fd9bb
Please, I need help on inserting values from multiple select options of a Dropdown into the Database and also a way to display that back upon viewing the profile edit page again.
I'm using semantic-ui-react in react + rails app.
Also when I insert a value using a normal drop down without multiple select, the value gets persisted into the database.
<Dropdown
placeholder='Select Country'
fluid
search
selection
options={countryOptions}
name='country'
defaultValue={this.state.extraInfo.country}
onChange={(e) => this.handleExtraInfoChange('country', e)}
/>
This code handles change for the dropdown elements.
handleExtraInfoChange = (name, event) => {
let value;
if (event.target.value !== undefined) {
value = event.target.value;
} else {
value = event.target.textContent;
}
let newExtraInfo = Object.assign(this.state.extraInfo, { [name]: value })
this.setState({ extraInfo: newExtraInfo});
}
But when I visit the page again, I get a white blank in the input box. Here's a screen pic for that. When I comment out the defaultValue or value property(i have tried with defaultValue and value), the white blank disappears, but the value picked by a user is also not seen.
Please advice what is a possible solution to this misbehavior? And what is the best way to insert multiple values into the Database?
Thanks in advance for your time.
A functional component does not have state, it's used for composition; you want to store state, so you either have to create a Component class or you need an external state container like redux.

Manipulating parent's array prop - Reactjs

This question may sound a little complicated. But i am stuck here, so i am throwing it here.
I am rendering a child component multiple times in parent with different properties and an array.
Parent contains an array of headings, i am passing it to child components and mapping select options with that array. If an heading is selected from any of the child components, that heading should display as disabled in other child components.
Child:
{
this.props.xarray.map((heading, index) => {
if (heading.headingis.toLowerCase().indexOf(this.state.field) != -1) {
this.setcheckstate(); //ignore this function
this.props.actionis(index, 'disabled'); //Using this function (passed from parent) to disable selected option
return <option value={heading.headingis} key={index} selected disabled={heading.disabled}>{heading.headingis}</option>
}else{
return <option value={heading.headingis} key={index} disabled={heading.disabled}>{heading.headingis}</option>
}
})
}
Parent's actionis function:
handlerHeading(index, disabled) {
xarray[index]['disabled'] = disabled;
}
It's working somehow BUT problem is
If first component is rendered, it will disable that value
Then 2nd component is rendered, it will disable that value
along with previous one.
and so on...
But in first component, only one value is disabled, in 2nd component 2 values are disabled and in 3rd component 3 values are disabled. And so on...
But i want if one value is disabled in component 6, it should get disabled in all previous components.
Have a look at following images for example.
Component #1 Rendered:
Component #4 Rendered:
I have resolved this, by changing xarray to state of parent.
This:
state = {xarray: xarray}
And:
handlerHeading(index, disabled) {
xarray[index]['disabled'] = disabled;
this.setState({ xarray: xarray });
}
For some reason you cannot change Props as you desire. See This Question

React.JS, pass data between components

very new to react. you can say I have not yet started to think like React.
here is the problem:
<div>
<DropDown> </DropDown>
<Panel> </Panel>
</div>
In the dropdown, I select a value. Store it in state, as something as , currentLocation.
Then I go to Panel, hit a button, and I want to open a modal. When i open a modal, I need to pass the currentLocation to that model.
I can pass in arbitrary value to modal, but I cannot figure out a way to get the currently selected item from DropDown.
How do I get the value of the currently selected item to the Panel?
Am I even making sense?
When you call the setState in the dropdown that will force an update of the page.
Then if you call this.state in your component you should have the value you need there.
You should go over the basic tutorials to grasp the react basics.
But it goes like this:
getInitialState: function() {
return {
myVar: ''
//set your variables here
//getInitialState will get called before the component gets mounted
};
},
myCustomFunction: function(newVariable) {
this.setState({
myVar: newVariable
});
},
render: function() {
return (
<div>
<input
onChange={this.myCustomFunction}
/>
<MyCustomComponent myProp={this.state.myVar}/>
//everytime the state changes MyCustomComponent will have that new value as a prop
</div>
);
}
There is a lot of ambiguity in your question but I'll try for the simplest case.
You have a Panel component and a Dropdown component.
You want to the Panel to have access to a value that was set when the Dropdown was used.
Solution: When the Dropdown is actuated, it creates an Action that Stores the selected value.
When the modal button in the Panel is actuated, it creates an Action that requires the DropDownStore. Then it decides what to do based on that value.
The pattern I am loosely describing is known Facebook's Flux architecture which is basically just a more specific application architecture for React applications similar to pub/sub or an event bus.

Categories