SolidJS: For in child component not rendering list - solid-js

I have a parent component returning:
<List list={list()}>
{(item, index) => <div>{item}</div>}
</List>
where list is a created signal. List is a custom component I made returning:
<div>
<For each={list}>{children}</For>
</div>
but whenever list is updated, it isn't rendered. When I move the For code to the parent, it renders, so what is it about passing a signal's value to a child component that makes it not rerender on updates?
EDIT: demo
import { render } from "solid-js/web";
import { createSignal, For } from "solid-js";
function Counter() {
const [list, setList] = createSignal([]);
const increment = () => setList([...list(), 1]);
return (
<>
<button type="button" onClick={increment}>
add
</button>
<p>broken list</p>
<List list={list()} />
<p>other list</p>
<For each={list()}>
{(item) => <p>{item}</p>}
</For>
</>
);
}
function List({list}) {
return (
<For each={list}>
{(item) => <p>{item}</p>}
</For>
);
}
render(() => <Counter />, document.getElementById("app"));
EDIT 2: I meant to use <List list={list()} />, which also doesn't work, but I missed it earlier.

It does not work because destructuring props in Solid loses reactivity, that is, all the destructured props values do not update, ever.
Destructuring props is sometimes convenient and commonly used in other frameworks, but is not really recommended in Solid - the FAQ says:
By destructuring, you separate the value from the object, giving you the value at that point in time and losing reactivity.
You need to rewrite List component to use single props parameter and access props.list in the JSX:
function List(props) {
return (
<For each={props.list}>
{(item) => <p>{item}</p>}
</For>
);
}
Why destructuring does not work? In Solid, props is an object, created behind the scenes by Solid, with getters for intercepting access to each individual property, like props.something. It's needed to track JSX (expressions and fragments) and effects (created by createEffect()) so that they are reevaluated and updated when props.something changes. There's no way to track access to properties which are destructured (well there's plugin for that, but it's not in the core framework because it has some overhead).

Related

change is slice state causing nonrelated components that fall under store to render?

as I understood from all the tourtilars that redux benefit is to stop excressive rendering, as oposite to context API but in my current app case that is not the case each change in my redux store casues a rerender. even to items that are not realted to the component, jsut emmiting a dispach that changes store value is casing a rerender... belw is my current code strucrue
import React from 'react';
// Redux
import { useAppSelector, useAppDispatch } from '../../app/hooks';
import { /* dispatch*/ } from '../../app/globalSlice';
const CentralHub: React.FC = () => {
const classes = useStyles();
const location = useLocation();
const selector = useAppSelector((state) => state);
const dispatch = useAppDispatch();
const testRender= ()=>{
// dispatch call that changes a store value
}
console.log('Render triggered');
return (
<ThemeProvider theme={darkTheme : lightTheme}>
<CssBaseline />
<AppBar />
<SnackBar />
<DynamicModal />
<div onClick={testRender}>click me</div>
</ThemeProvider>
);
};
export default CentralHub;
the issue is for example if I change the store value using dispatch the app bar retenders as well the main component, I thought the concept was to isolate state change from being related to all components under the wrapper to one that is making the change , any idea how to prevent this or at least what is the right mind set to follow.

How to handle state in Material UI Data Grid's components

I'm attempting to add search functionality to a data grid component. In order to achieve this, I'm adding an input element to the table using the components.header prop as follows (I've omitted irrelevant code):
const Table = () => {
const filterRows = (rows) => {
return rows;
};
const [searchTerm, setSearchTerm] = useState("");
const Header = () => (
<input
value={searchTerm}
onChange={(event) => setSearchTerm(event.target.value)}
/>
);
return (
<div style={{ height: 500 }}>
<DataGrid
rows={searchTerm ? filterRows(orders) : orders}
columns={orderColumns}
components={{
header: () => <Header />
}}
/>
</div>
);
};
The issue I'm having is that the input element loses focus each time a character is entered into the input in the header. Presumably, this is because it updates state, which triggers a re-render. This makes it impossible to share and access state of anything contained inside the Data Grid's components because it requires a React.FC argument and won't accept a ReactElement so the input is always re-rendered.
Am I missing something or is this actually not possible with Material UI's Data Grid? It seems like a pretty expected use-case to have something stateful in the header that we'd want to access like a controlled input component in order to use it as a kind of "Tool bar" as mentioned in the Material UI docs.
I've created a code sandbox to replicate the issue here: https://codesandbox.io/s/compassionate-keldysh-z995k?file=/src/App.js:246-726.
Cheers.

How can I make StencilJS component to render without component tag itself with TSX?

While I understand this is probably a terrible practice, I need to build StencilJS component such that inside render(), I don't want to render component tag itself due to already existing style guide and it expect DOM to be constructed in certain way. Here is what I'm trying to achieve - component code (from HTML or within another component):
<tab-header-list>
<tab-header label="tab 1"></tab-header>
<tab-header label="tab 2"></tab-header>
</tab-header-list>
when rendered, I want generated DOM to be something like:
<tab-header-list>
<ul>
<li>tab 1</li>
<li>tab 2</li>
</ul>
</tab-header-list>
so inside tab-header-list render() function, I'm doing
return (
<ul>
<slot/>
</ul>
);
and I can do this inside tab-header render() function
#Element() el: HTMLElement;
#Prop() label: string;
render() {
this.el.outerHTML = `<li>${this.label}</li>`;
}
to get what I want but how can I do this with TSX? (for simplicity sake, above code is really simple but what I really need to build is lot more complicated li tag with events etc so I would like to use TSX)
Tried to store DOM to variable but I'm not sure how I can assign it as this.el (outerHTML seem to be only way I can come up with, but I feel there must be better way)
#Element() el: HTMLElement;
#Prop() label: string;
render() {
var tabheaderDOM = (<li>{this.label}</li>);
// how can I assign above DOM to this.el somehow?
//this.el.outerHTML = ?
}
I appreciate any help I can get - thanks in advance for your time!
Unfortunately, you can't use custom elements without tags, but there is a workaround for it:
You can use Host element as reference to the result tag.
render () {
return (
<Host>....</Host>
)
}
Then in your stylesheet you can set the display property for it:
:host {
display: contents;
}
display: contents causes an element's children to appear as if they were direct children of the element's parent, ignoring the element itself
Beware: it doesn't work in IE, opera mini... https://caniuse.com/#feat=css-display-contents
UPD:
If you are not using the shadowDOM then you need to replace :host by the tag name like:
tab-header {
display: contents;
}
Functional components might be able to help you achieve this. They are merely syntactic sugar for a function that returns a TSX element, so they are completely different to normal Stencil components. The main difference is that they don't compile to web components, and therefore only work within TSX. But they also don't result in an extra DOM node because they simply return the template that the function returns.
Let's take your example:
#Element() el: HTMLElement;
#Prop() label: string;
render() {
this.el.outerHTML = `<li>${this.label}</li>`;
}
you could write it as a functional component:
import { FunctionalComponent } from '#stencil/core';
interface ListItemProps {
label: string;
}
export const ListItem: FunctionalComponent<ListItemProps> = ({ label }) => (
<li>{label}</li>
);
and then you can use it like
import { ListItem } from './ListItem';
#Component({ tag: 'my-comp' })
export class MyComp {
render() {
return (
<ul>
<ListItem label="tab 1" />
<ListItem label="tab 2" />
</ul>
);
}
}
Which will render as
<ul>
<li>tab 1</li>
<li>tab 2</li>
</ul>
Instead of a label prop you could also write your functional component to accept the label as a child instead:
export const ListItem: FunctionalComponent = (_, children) => (
<li>{children}</li>
);
and use it like
<ListItem>tab 1</ListItem>
BTW Host is actually a functional component. To find out more about functional components (and there limitations), see https://stenciljs.com/docs/functional-components.

Extract function to standalone custom React component in react-leaflet

My primary goal is to call fitBounds whenever a FeatureGroup is rendered in react-leaflet on initial load.
This renders correctly -
<Map>
<LayersControl>
{getLayers(groups)}
</LayersControl>
</Map>
function getLayers(featureGroups: MyFeatureGroup[]){
const showOnLoad = true;
return featureGroups.map((group: MyFeatureGroup) => {
const groupRef = createRef<FeatureGroup>();
const { id, name, } = group;
return (
<LayersControl.Overlay checked={showOnLoad} key={id} name={name}>
<FeatureGroup ref={groupRef}>
<Layer {...group} />
</FeatureGroup>
</LayersControl.Overlay>
);
});
}
However, because it is using a function instead of React component, I don't have access to using React hooks.
The alternative that I tried does not work, even though it is the same code wrapped in a React component -
...same as above...
return featureGroups.map((group: MyFeatureGroup) => (
<ControlledGroup {...group} showOnLoad={showOnLoad} /> ///----- > ADDED THIS HERE
));
const ControlledGroup: React.FC<ControlledGroupProps> = (props) => {
const groupRef = createRef<FeatureGroup>();
const { map } = useLeaflet();
/// -----> map is correctly defined here - injecting to all of the layers (LayersControl, FeatureGroup) does not solve the problem
const { showOnLoad, ...group } = props;
useEffect(() => fitBounds(map, groupRef)); ///-----> Primary Goal of what I am trying to accomplish
return (
<LayersControl.Overlay
checked={showOnLoad}
key={group.id}
name={name}
>
<FeatureGroup ref={groupRef}>
<Layer map={map} {...group} />
</FeatureGroup>
</LayersControl.Overlay>
);
};
I am a bit stumped, since this is the same code. The getLayers function returns a ReactNode in both cases. However, when moving to a standalone ControlledGroup component, it throws an error on render -
addOverlay is not a function
I tried creating a custom class component for react-leaflet, but the difficulty that I ran into there is that createLeafletElement returns a Leaflet.Element, whereas I am simply looking to return a ReactNode. That is, all of these are valid react-leaflet components already.
My questions - why does one work and the other does not? What is the correct/recommended way to convert this function to a renderable stand-alone React component?
Further, if there is an alternative pattern to calling fitBounds, that would be helpful as well.
Any insight would be appreciated.
Since the Layers share an inheritance with Layers.Overlay, the solution to the render error is to keep the Layers together and move the feature group to a standalone component.
This works as expected and allows me to call useEffect on the groupRef -
function getLayers(groups: MyFeatureGroup[]){
return featureGroups.map((group: MyFeatureGroup) => {
const { id, name, } = group;
return (
///---> Keep the Overlay in the function here and extract just the FeatureGroup out
<LayersControl.Overlay checked={showOnLoad} key={id} name={name}>
<ControlledGroup {...group}></ControlledGroup>
</LayersControl.Overlay>
);
}

In Reactjs how do I update the correct value in the parent state with a callback that's been passed to a nested child component?

I've been on this one for days, and all my reading hasn't helped me find a clean solution for this particular case.
Issue
I can send a parent state value and callback down to a nested component, but once the callback is triggered in the child I don't know how I can send the updated value back to the parent so it can update the correct value.
For instance
Parent Component (Has values and the callback)
Child Component (Values and callback is passed here)
Grand Child Component (Values Updated here and callback triggered)
What is SEEMS to cause the Issue
It seems the issue is I need the original key name in order for "setState" to update the correct value in the parent component(or at least it seems that way), but the child component only has original value and new updated value and has no access to the key associated with original value in the parent component.
Important Notes on Best Practice Surrounding this question
-From what I understand it is bad practice to use refs to handle nested situations like this.
-It seems like there is a cleaner solution than sending a prop for the key and another for the value.
-I'm assuming also that flux might provide a solution to this issue but I feel that there is a basic component to component communication technique or principle that I'm missing here.
Here is a bare bones example of what I'm dealing with.
/*All the values need to be updated here so that the inputs can used for calculation and then sent to a component that displays the output*/
var Calculator =
React.createClass({
getInitialState:function(){
return {
value1: "Enter value 1", /*These values are passed to a nested child component, can't figure how to update the right one*/
value2: "Enter value 2",
}
},
update: function(update){
this.setState(
update
);
},
render: function () {
return (
<div>
<h2>Input</h2>
<Input onClick={this.handleClick} update={this.update} value1={this.state.value1} value2={this.state.value2} /> //pass the values here
<h2>Output</h2>
<Output />
</div>
);
},
handleClick: function () {
//want to update the state for the correct value here
}
});
/* A compenent that is a middle layer between the parent and nested child component I'm working with*/
var Input =
React.createClass({
update: function(){
this.props.update();
},
render:function(){
return (
<div>
<p><InputComponent update={this.update} value={this.props.value1} /> / <InputComponent value={this.props.value2}/></p>//passing down values again
<p><ButtonComponent onClick={this.props.onClick} /></p>
</div>
)
}
});
/*This is the child component that gets the value and call back from the top level component. It will get updates to the values and send them back to change state of the parent component.*/
var InputComponent =
React.createClass({
handleChange: function(event) {
this.props.update();
},
render: function() {
return <input type="text" value={this.props.value} onChange={this.handleChange} />; //this props value has no key associated with it. Cant't make update object ie {originalkey:newValue}
}
});
/* This component is triggered to carry out calculations in the parent class.*/
var ButtonComponent =
React.createClass({
render:function(){
return <button onClick={this.handleClick}> {this.props.txt} </button>
},
handleClick: function(){
this.props.onClick();
}
});
/*The inputs will be calculated and turned to outputs that will displayed here.This component doesn't matter for the question so I left it empty*/
var Output =
React.createClass({
});
Here's an example I just put together on jsfiddle.
Instead of putting update in setState, we pass a value to update from the child component and let the parent set its state.
In the parent, we have:
_update: function(val){
this.setState({
msg: val
});
},
render: function() {
return (
<div>
<p>Message: {this.state.msg}</p>
<Child _update={this._update} />
</div>
);
}
And in the child, we have a _handleClick function that calls the parent _update function with values:
_handleClick: function(){
this.props._update(React.findDOMNode(this.refs.text).value);
},
render: function(){
return (
<div>
<input type="text" ref="text" />
<button onClick={this._handleClick}>Update</button>
</div>
);
}