React-Redux: How do I access the store in Parent Component? - reactjs

So I have a React Native application and recently added Redux to it.
Now I am stuck with following problem:
There is a child component (a number slider where you can set the height value of an item) which is called by a parent component. Every time the value of the number slider in the child component changes, I want to have a console.log of the updated value in the parent component.
Therefore, the parent component somehow must have access to the Redux store, but I can't figure out how to do this. I tried converting the parent component into a Class Component and call store.getState();, but this only gives the initial value of the store and is not updated at all.
So I went back to the parent being a Functional Component and implemented the desired behavior without Redux, but with a callback function. But what do I have Redux for when I'm not using it? In the future I will definitely need Redux in this project and therefore, it would be great to solve this issue using it.
Here is the code without the callback function:
Parent.tsx
imports ...
import Child from '../../atoms/Child/Child';
import store from '../../../../index.js';
const Parent: React.FC<Props> = () => {
// console.log('store: ', store.getState());
const renderChild= () => {
return (
<View>
<Child></Child>
</View>
);
};
const dataArray = [{content: renderChild()}];
return (
<Accordion
dataArray={dataArray}
/>
);
};
export default Parent;
Child.tsx
imports ...
import {connect} from 'react-redux';
import {RootState} from '../../../rootReducer/rootReducer';
import {setHeight} from '../../../store/child/actions';
import {HeightState} from '../../../store/child/types';
type Props = {};
const Child: React.FC<Props> = () => {
const [sliderValue, setSliderValue] = useState(65);
useEffect(() => {
setHeight({height: sliderValue});
}, []);
useEffect(() => {
setHeight({height: sliderValue});
}, [sliderValue]);
return (
<View>
<Text>Height is {sliderValue} inches</Text>
<Slider
step={1}
value={sliderValue}
onValueChange={sliderValue => setSliderValue(sliderValue)}
/>
</View>
);
};
function mapStateToProps(state: RootState) {
return {
height: state.heightResult.height,
};
}
const mapDispatchToProps = {
setHeight,
};
export default connect(mapStateToProps, mapDispatchToProps)(Child);
According Redux Devtools, the store is updated correctly and works fine.
Can you please help me?

You need to connect the parent to the store also. At the moment your Parent has no idea of the store and has no relationship with it. This is the whole point of redux, flexibility and scalability of the state.
const Parent: React.FC<Props> = ({height}) => {
console.log('height', height);
};
const mapStateToProps = ({ heightResult: { height }}: RootState) => ({ height });
export default connect(mapStateToProps)(Parent);

Related

how to handle props between components in react

i have came accross a problem where i am passing three props to a component Landingheader from parent Landing.js now i have another component called Cart and i want to use LandingHeader
as child component inside Cart but then i would also have to pass all the three props again to Landingheader which is very difficult and alot of code to rewrite
here is the code in Landing.js
<div>
<Landingheader
fetchproductResults={fetchproductResults}
user={user}
cartValue={cartValue}
/>
above you can see landingHeader component is getting three differenct props
here is my cart component where i want to resuse landingHeader component
import { Fragment } from "react";
import Landingheader from "./landingHeader";
const Cart = () => {
return (
<Fragment>
<Landingheader />
</Fragment>
);
}
export default Cart;
so above the landingHeader will now require three props so this means i would have to rewrite the whole logic again? how to solve this propblem? thanks
code for fetchproductResults
const fetchproductResults = (keyword) => {
setWord(keyword);
if (keyword !== "") {
const searchedRs = allproducts.filter((eachproduct) => {
return Object.values(eachproduct)
.join("")
.toLowerCase("")
.includes(keyword.toLowerCase());
});
setResult(searchedRs);
} else {
setResult(allproducts);
}
};
In case you don't need to pass any props to Landingheader from Cart you could use default value props in Landingheader. Something like:
const Landingheader = (props) => {
const { fetchproductResults = [], user = "", cartValue = "" } = props;
return (...);
}
export default Landingheader;
You can use context instead of props
in Landing component:
const MyContext=createContext(null)
const Landing=()=>{
.......
return (<MyContext.Provider value={[fetchproductResults,user,cartValue]}>
... all child compoenents
</MyContext.Provider/>
Now in Landingheader :
const [fetchproductResults,user,cartValue]=useContext(MyContext) /// use them as you like
Now you don't need to pass any props to either Cart or LandingHeader, it is receiving the data through context.

Why updaing a MobX observable property doesn't work when passed as a reference

I'm experimenting with MobX, and would like to understand a basic thing regarding an observable update.
In the following code, store.parentState.counter is passed from Parent to Child. Both Parent and Child have an increment button that's supposed to update counter.
However, only Parent's button updates the counter.
Why is that? Also, can we make the child button work?
import React from "react";
import { observable, configure } from 'mobx'
import { observer } from 'mobx-react-lite'
const store = observable({
parentState: {
counter: 0
}
})
const Parent = observer((props) => {
const increment = () => { store.parentState.counter += 1; };
return (
<div>
<span>Parent: {store.parentState.counter}</span>
<button onClick={increment}>increment</button>
<Child parentCounter={store.parentState.counter} />
</div>
);
});
const Child = observer(({parentCounter}) => {
const increment = () => { parentCounter +=1 ; };
return (
<div>
<span>Child</span>
<button onClick={increment}>increment</button>
</div>
);
});
configure({
enforceActions: "never",
})
export default Parent;
Live demo.
EDIT:
Just to clarify, I'm aware that with this example, the child can simply update the store directly, so that passing a reference to counter doesn't make much sense. Alternatively, as suggested in the comments, the child can be passed the store.
But, as mentioned, I'm experimenting with MobX, and would like to understand the Why (and whether it can be made to work).
[Edit]: You are passing the counter directly in the prop. I assume that since it's a primitive, it is passed as value (Instead of ref as for objects) and therefore does not have any bind to the store. Passing the appState node works well as shown in this Stackblitz example. Here is the code just in case :
import React from "react";
import { render } from "react-dom";
import { observable } from "mobx";
import { observer } from "mobx-react-lite";
const store = observable({
appState: {
counter: 0
}
});
const App = observer(() => {
const increment = () => (store.appState.counter += 1);
return (
<div>
Counter: {store.appState.counter}
<br />
<button onClick={increment}>Increment</button>
<hr />
<AppChild appState={store.appState} />
</div>
);
});
const AppChild = observer(({ appState }) => {
const increment = () => (appState.counter += 1);
return (
<>
Counter: {store.appState.counter}
<br />
<button onClick={increment}>Increment from child</button>
</>
);
});
render(<App />, document.getElementById("root"));
I don't know MobX but since your store is global, as in other store manager, it makes no sense to pass a value from your parent to a component wrapped with observer(). just doing as follow works :
import React from "react";
import { observable, configure } from "mobx";
import { observer } from "mobx-react-lite"; // 6.x or mobx-react-lite#1.4.0
import "./styles.css";
const store = observable({
parentState: {
counter: 0
}
});
const Parent = observer((props) => {
const increment = () => {
store.parentState.counter += 1;
};
return (
<div>
<span>Parent: {store.parentState.counter}</span>
<button onClick={increment}>increment</button>
<Child parentCounter={store.parentState.counter} />
<Child2 updateCounter={increment} />
</div>
);
});
const Child = observer(() => {
const increment = () => {
console.log(store.parentState.counter);
store.parentState.counter += 1;
};
return (
<div>
<span>Child</span>
<button onClick={increment}>increment</button>
</div>
);
});
const Child2 = ({updateCounter}) => {
return (
<div>
<span>Child2</span>
<button onClick={updateCounter}>increment</button>
</div>
);
}
configure({
enforceActions: "never"
});
export default Parent;
I added a Child2 component to show you how you could do it by using a parent method to update your store through a component that is not wrapped with observer().
Now, since it makes no sense to me, there might be a solution to do it I don't know, but I don't see any case where you need to do that.
Found the answer in MobX documentation: MobX tracks property access, not values.
MobX reacts to any existing observable property that is read during
the execution of a tracked function.
"reading" is dereferencing an object's property, which can be done
through "dotting into" it (eg. user.name) or using the bracket
notation (eg. user['name'], todos[3]).
"tracked functions" are the
expression of computed, the rendering of an observer React function
component, the render() method of an observer based React class
component, and the functions that are passed as the first param to
autorun, reaction and when.
"during" means that only those observables
that are read while the function is executing are tracked. It doesn't
matter whether these values are used directly or indirectly by the
tracked function. But things that have been 'spawned' from the
function won't be tracked (e.g. setTimeout, promise.then, await etc).
In other words, MobX will not react to:
Values that are obtained from observables, but outside a tracked
function
Observables that are read in an asynchronously invoked code
block
Hence, passing store.parentState and accessing counter in the child will make it work.

React child component does not re-render when props passed in from parent changes

I have a simplified react structure as below where I expect MyGrandChildComponent to re-render based on changes to the 'list' property of MyParentComponent. I can see the list take new value in MyParentComponent and MyChildComponent. However, it doesnt even hit the return function of MyGrandChildComponent. Am i missing something here?
const MyGrandChildComponent = (props) => {
return (
<div>props.list.listName</div>
);
};
const MyChildComponent = (props) => {
return (
<div><MyGrandChildComponent list={props.list}/></div>
);
}
const MyParentComponent = (props) => {
const list = { listName: 'MyList' };
return (
<div><MyChildComponent list={list} /></div>
);
}
In your MyParentComponent, the list is not a state variable and as such changing it will not even cause a re-render. If you absolutely want that when ever you change the value of list it re renders, then you will want to bring state to your functional component and the way to do that is to use hooks.
In this case your parent component will be something like below
import React, {useState} from 'react'
const MyParentComponent = (props) => {
const [list, setList] = useState({ listName: 'MyList' });
return (
<div><MyChildComponent list={list} /></div>
);
}
then at the child component you render it as I suggested in the comment above.
The parent needs to hold the list as a state variable and not just as a local variable. This is because react rerenders based on a state or prop change and at the parent you can only hold it in the state. With this when the value of list changes there will be a re-render which will then propergate the change to the children and grandchildren.
Also the only way of maintaining state in a functional component is to use hooks.
const MyGrandChildComponent = (props) => {
return (
<div>{props.list.listName}</div>
);
};
You forgot the {} around props.list.listName

React.memo - why is my equality function not being called?

I have a parent component that renders a collection of children based on an array received via props.
import React from 'react';
import PropTypes from 'prop-types';
import shortid from 'shortid';
import { Content } from 'components-lib';
import Child from '../Child';
const Parent = props => {
const { items } = props;
return (
<Content layout='vflex' padding='s'>
{items.map(parameter => (
<Child parameter={parameter} key={shortid.generate()} />
))}
</Content>
);
};
Parent.propTypes = {
items: PropTypes.array
};
export default Parent;
Every time a new item is added, all children are re-rendered and I'm trying to avoid that, I don't want other children to be re-rendered I just want to render the last one that was added.
So I tried React.memo on the child where I'll probably compare by the code property or something. The problem is that the equality function never gets called.
import React from 'react';
import PropTypes from 'prop-types';
import { Content } from 'components-lib';
const areEqual = (prevProps, nextProps) => {
console.log('passed here') // THIS IS NEVER LOGGED!!
}
const Child = props => {
const { parameter } = props;
return <Content>{parameter.code}</Content>;
};
Child.propTypes = {
parameter: PropTypes.object
};
export default React.memo(Child, areEqual);
Any ideas why?
In short, the reason of this behaviour is due to the way React works.
React expects a unique key for each of the components so it can keep track and know which is which. By using shortid.generate() a new value of the key is created, the reference to the component changes and React thinks that it is a completely new component, which needs rerendering.
In your case, on any change of props in the parent, React will renrender all of the children because the keys are going to be different for all of the children as compared to the previous render.
Please reference this wonderful answer to this topic
Hope this helps!
I was having the same issue and the solution turned out to be just a novice mistake. Your child components have to be outside of the parent component. So instead of:
function App() {
const [strVar, setStrVar] = useState("My state str");
const MyChild = React.memo(() => {
return (
<Text>
{strVar}
</Text>
)
}, (prevProps, nextProps) => {
console.log("Hello"); //Never called
});
return (
<MyChild/>
)
}
Do it like this:
const MyChild = React.memo(({strVar}) => {
return (
<Text>
{strVar}
</Text>
)
}, (prevProps, nextProps) => {
console.log("Hello");
});
function App() {
const [strVar, setStrVar] = useState("My state str");
return (
<MyChild strVar = {strVar}/>
)
}
Another possibility for unexpected renders when including an identifying key property on a child, and using React.memo (not related to this particular question but still, I think, useful to include here).
I think React will only do diffing on the children prop. Aside from this, the children prop is no different to any other property. So for this code, using myList instead of children will result in unexpected renders:
export default props => {
return (
<SomeComponent
myLlist={
props.something.map(
item => (
<SomeItem key={item.id}>
{item.value}
</SomeItem>
)
)
}
/>
)
}
// And then somewhere in the MyComponent source code:
...
{ myList } // Instead of { children }
...
Whereas this code (below), will not:
export default props => {
return (
<SomeComponent
children={
props.something.map(
item => (
<SomeItem key={item.id}>
{item.value}
</SomeItem>
)
)
}
/>
)
}
And that code is exactly the same as specifying the children prop on MyComponent implicitly (except that ES Lint doesn't complain):
export default props => {
return (
<SomeComponent>
{props.something.map(
item => (
<SomeItem key={item.id}>
{item.value}
</SomeItem>
)
)}
</SomeComponent>
)
}
I don't know the rest of your library but I did some changes and your code and (mostly) seems to work. So, maybe, it can help you to narrow down the cause.
https://codesandbox.io/s/cocky-sun-rid8o

How to map encapsulated ui state in a redux store to props with react-redux?

I'm having trouble figuring out how to compose ui widget reducers and react render trees in tandem, in a way that I can later map the resulting redux store for the reducers to props in the render tree using react-redux.
Suppose a setup like this. I'm trying to make a NameWidget that I can use anywhere in any app:
NameView.jsx:
...
render() {
return <div> Name: {this.props.name} </div>;
}
...
====
NameAction.js:
...
export const CHANGE_NAME = 'CHANGE_NAME';
...
export function changeNameAction(newName) {
return {
type: CHANGE_NAME,
payload: newName,
};
}
====
NameReducer.js:
import { CHANGE_NAME } from './NameAction';
function ui(state = {name: ''}, action) {
switch(action.type) {
case(CHANGE_NAME):
return Object.assign({}, state, {name: action.payload});
break;
default:
return state;
}
}
export default ui;
====
NameWidget.js:
import { connect } from 'react-redux';
import { NameView } from './NameView';
const mapStateToProps = (state) => {
return {
name: state.name,
};
};
const NameWidget = connect(
mapStateToProps
)(NameView);
export default NameWidget;
If I use NameWidget directly I'll have no problem:
NameApp.jsx:
import { createStore } from 'redux';
import app from './NameReducers';
let store = createStore(app);
...
function render() {
return (
<Provider store={store}>
<NameWidget/>
</Provider>
);
}
However, I don't see how to make this modular. I can't actually put this Widget into anything else. Suppose I wanted to do this:
EncapsulatingView.jsx:
...
render() {
return (
<div>
<NameWidget/>
<SomeOtherReduxWidget/>
</div>
);
}
...
====
EncapsulatingReducer.js:
import { combineReducers } from 'redux'
import nameWidget from '../name/NameReducer';
import someOtherWidget from '../someOther/SomeOtherReduxWidgetReducer';
export default combineReducers({nameWidget, someOtherWidget});
(I expect that the remaining code including connect() and createStore() for Encapsulating* is obvious.)
This almost works, as long as all the actions across all encapsulated views have unique types. But the problem is NameWidget's mapStateToProps; it needs to change from above to use a new path to its section of the store:
...
const mapStateToProps = (state) => {
return {
name: state.nameWidget.name,
};
};
...
And so on - depending on how ancestors combineReducers() to define their ever-grander super widgets and apps, the descendants somehow have to change how they mapStateToProps() to compensate. This breaks encapsulation and reusability of code, and I don't see how to get around it in react-redux. How can I define widgets by composing combineReducers(), and then map the resulting store to those widgets' props, in a way that's write-once-use-anywhere?
(Apropos of nothing, it seems strange that repeated combineReducers() creates a shape that's bottom up, but that mapStateToProps() seems to assume instead a top-down "absolute path" approach to looking up into that shape.)
Interesting question. I wonder if it would serve your purpose to require a guid be generated by the parent and passed down through props, and then you simply use that as the index of your state object for that instance of your widget. So whenever you are about to create a new instance of this widget you need to create an ID for it in the parent, and then pass that down in the props, then it will be available to the connect method to use in both mapStateToProps and mapDispatchToProps. I suppose in theory you could even create this parent component yourself and it could possibly be transparent when it is being used, and simply use componentDidMount to generate a new guid to store in component state and pass down to the child.
I gave John the credit for this question because he essentially provided the right magic for the answer, passing store "routing" logic via mapStateToProps()'s ownProps function arg. But I preferred my own spin on it. Essentially, whenever you combineReducers() in an EncapsulatingReducer.js, you have to remember to pass a uiStorePath prop in EncapsulatingView.js:
New NameWidget.js:
const _ = require('lodash');
import { connect } from 'react-redux';
import { NameView } from './NameView';
const mapStateToProps = (state, props) => {
const uiStore = (
(ownProps && _.has(ownProps, 'uiStorePath') && ownProps.uiStorePath &&
ownProps.uiStorePath.length) ?
_.get(state, ownProps.uiStorePath) : // deep get...old versions of lodash would _.deepGet()
state
);
return {
name: uiStore.name,
};
};
const NameWidget = connect(
mapStateToProps
)(NameView);
export default NameWidget;
====
EncapsulatingView.js:
...
render() {
const uiStorePathBase = ((this.props.uiStorePath &&
this.props.uiStorePath.length) ?
this.props.uiStorePath + '.' :
''
);
return (
<div>
<NameWidget uiStorePath={uiStorePathBase+"nameWidget"}/>
<SomeOtherWidget uiStorePath={uiStorePathBase+"someOtherWidget"/>
</div>
);
}
...

Resources