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

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.

Related

Is there a useState concept so I can create a service which holds data that is accessed in multiple components?

I have 2 components who want to access the same data. Instead of each doing an HTTP Request independantly, I wanted to keep the items in parity. When doing react, we can easily do: const [ data, setData ] = useState(undefined) which will allow us to use data in our app and setData to change the global.
I was trying to think of how this might be doable in ReactScala, and Was thinking that there could be some overlap here since you can do something like:
useState[A]( data: A ): Pair[A, A=>A] = {
val d = data
return d, x => {
d = x
return d
}
}
or similar.
I have not seen the documentation on useState in Japgolly as much as defining the property in the component state and then using the state.copy() function to update the value.
The issue which occurred is that to me, state.copy is just 1 component, and wanted to know if there was a way to genericize.
https://github.com/japgolly/scalajs-react/blob/master/doc/HOOKS.md
Under the HOOKS file linked above, the top example shows how useState is translated. I will add it below in case the file is changed or deleted:
import React, { useState, useEffect } from 'react';
function Example() {
const [count, setCount] = useState(0);
useEffect(() => {
document.title = `You clicked ${count} times`;
});
const [fruit, setFruit] = useState("banana");
return (
<div>
<p>You clicked {count} times</p>
<button onClick={() => setCount(count + 1)}>
Click me
</button>
<p>Your favourite fruit is a {fruit}!</p>
</div>
);
}
Compared to:
import japgolly.scalajs.react._
import japgolly.scalajs.react.vdom.html_<^._
import org.scalajs.dom.document
object Example {
val Component = ScalaFnComponent.withHooks[Unit]
.useState(0)
.useEffectBy((props, count) => Callback {
document.title = s"You clicked ${count.value} times"
})
.useState("banana")
.render((props, count, fruit) =>
<.div(
<.p(s"You clicked ${count.value} times"),
<.button(
^.onClick --> count.modState(_ + 1),
"Click me"
),
<.p(s"Your favourite fruit is a ${fruit.value}!")
)
)
}

SolidJS: For in child component not rendering list

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).

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.

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>
);
}

FOUC when using #material-ui/core with NextJS/React

My simple NextJS page looks like this (results can be viewed at https://www.schandillia.com/):
/* eslint-disable no-unused-vars */
import React, { PureComponent, Fragment } from 'react';
import Head from 'next/head';
import compose from 'recompose/compose';
import Layout from '../components/Layout';
import { withStyles } from '#material-ui/core/styles';
import Button from '#material-ui/core/Button';
const styles = {
root: {
textAlign: 'center',
paddingTop: 200,
},
p: {
textTransform: 'uppercase',
color: 'red',
},
};
class Index extends PureComponent {
render() {
const { classes } = this.props;
const title = 'Project Proost';
const description = 'This is the description for the homepage';
return (
<Fragment>
<Head>
<title>{ title }</title>
<meta name="description" content={description} key="description" />
</Head>
<Layout>
<p className={classes.p}>amit</p>
<Button variant="contained" color="secondary">
Secondary
</Button>
</Layout>
</Fragment>
);
}
}
export default withStyles(styles)(Index);
I am importing a bunch of components off the #material-ui/core library to style my items. I also have a local style definition assigned to a style constant.
What seems to be happening here is that my style isn't getting rendered on the server which is why the files being served upon load are sans-style. And then the CSS gets rendered by the client-side code. As a result, there's a flash of unstyled content that lasts almost a second, long enough to be noticable.
Any way to fix this? The entire codebase is up for reference at https://github.com/amitschandillia/proost/tree/master/web.
I ran a similar problem when tried to make a production build of my app, that uses material-ui. I manage to solve by adding a JSS Provider like this:
import JssProvider from "react-jss/lib/JssProvider";
class App extends Component {
render() {
<JssProvider>
*the rest of your material-ui components*
</JssProvider>
}
}
Here's the solution - https://github.com/mui-org/material-ui/blob/master/examples/nextjs/pages/_document.js .
Basically, all you need to do is to sync server-side class names with client-side. The link above shows what you need to do to fix that issue.