Need to update state on stateless component using react-redux - javascript

I have a component that is connected to a store using react-redux (I have shortened my code for simplicity sake):
const Address = (props) => {
const { userAddresses, removeAddress } = props
let { showEdit } = props
return (
<div>
{userAddresses.map(address => (
<div key={address.id}>
<p>{address.name}</p>
<Button onClick={removeAddress.bind(this, address)}>Delete</Button>
</div>
))}
<Button
onClick={() => { showEdit = true }}>
Add new
</Button>
{showEdit ? (
// show some edit stuff here!!!
): null}
</div>
)
}
const mapState = state => {
return {
userAddresses: state.account.userAddresses,
showEdit: false
}
}
const mapDispatch = (dispatch) => {
return {
addAddress: address => dispatch(addUserAddress(address)),
removeAddress: address => dispatch(removeUserAddress(address)),
}
}
export default connect(mapState, mapDispatch)(Address)
When you click the button (Add new), a form is supposed to popup (marked as show some edit stuff here!!!). I know this can be easily done if Address was a state component. However, I need to use react-redux, and as far as I know, you have to use a stateless component to use react-redux. Can anyone point me in the right direction?

No, you do not "have to use a stateless/function component" to use React-Redux!
connect accepts both class components and function components (and even "special" React components like React.memo()), and it doesn't matter whether they use component state (or hooks) inside or not.
Also, as a side note, you can simplify your code using the "object shorthand" form of mapDispatch:
const mapDispatch = {
addAddress : addUserAddress,
removeAddress : removeUserAddress
}
(Note that that could be even shorter if your prop names were named the same as the functions.)

Keeping to strictly use redux, you should have another action to dispatch when the user clicks the button. Then, a reducer will update the value of the showEdit property, which will cause a re-render of your stateless component allowing you to conditionally render the editing form.
But, this is an information (the visibility or not of the editing form) not useful to the rest of your application, so it could be the case to transform your component into a stateful one and track the showEdit property in the local state.
A third option could be the use of useState hook, but it depends on the version of React you have in your project, because they are currently in alpha...

Related

How to access state from function below component? [duplicate]

I have 2 react components that need to share a state, react-router shows component A, which takes some inputs and adds it to its state, after the state has been successfully updated, I want to redirect to component B, where the user adds some more inputs and updates the same state as component A to build an object with inputs from A and B before I submit a post request to my api to save the data from both component A and B. How can I accomplish this, is there a way to use react-router, or do I have to set up a parent/child relationship between the components?
The dependency type between the components will define the best approach.
For instance, redux is a great option if you plan to have a central store. However other approaches are possible:
Parent to Child
Props
Instance Methods
Child to Parent
Callback Functions
Event Bubbling
Sibling to Sibling
Parent Component
Any to Any
Observer Pattern
Global Variables
Context
Please find more detailed information about each of the approaches here
What you want is to implement some object that stores your state, that can be modified using callback functions. You can then pass these functions to your React components.
For instance, you could create a store:
function Store(initialState = {}) {
this.state = initialState;
}
Store.prototype.mergeState = function(partialState) {
Object.assign(this.state, partialState);
};
var myStore = new Store();
ReactDOM.render(
<FirstComponent mergeState={myStore.mergeState.bind(myStore)} />,
firstElement
);
ReactDOM.render(
<SecondComponent mergeState={myStore.mergeState.bind(myStore)} />,
secondElement
);
Now, both the FirstComponent and SecondComponent instances can call this.props.mergeState({ . . .}) to assign state to the same store.
I leave Store.prototype.getState as an exercise for the reader.
Note that you can always pass the store (myStore) itself to the components; it just feels less react-y to do so.
Here is some more documentation that might be of interest:
React Docs: "Communicate Between Components"
For communication between two components that don't have a
parent-child relationship, you can set up your own global event
system. Subscribe to events in componentDidMount(), unsubscribe in
componentWillUnmount(), and call setState() when you receive an event.
Flux pattern is one of the possible ways to arrange this.
The easiest way to use a shared state between several components without rewriting your application's code to some state management system is use-between hook.
Try this example in codesandbox
import React, { useState } from "react";
import { useBetween } from "use-between";
// Make a custom hook with your future shared state
const useFormState = () => {
const [username, setUsername] = useState("");
const [email, setEmail] = useState("");
return {
username, setUsername, email, setEmail
};
};
// Make a custom hook for sharing your form state between any components
const useSharedFormState = () => useBetween(useFormState);
const ComponentA = () => {
// Use the shared hook!
const { username, setUsername } = useSharedFormState();
return (
<p>
Username: <input value={username} onChange={(ev) => setUsername(ev.target.value)} />
</p>
);
};
const ComponentB = () => {
// Use the shared hook!
const { email, setEmail } = useSharedFormState();
return (
<p>
Email: <input value={email} onChange={(ev) => setEmail(ev.target.value)} />
</p>
);
};
const ComponentC = () => {
// Use shared hook!
const { email, username } = useSharedFormState();
return (
<p>
Username: {username} <br />
Email: {email}
</p>
);
};
export const App = () => (
<>
<ComponentA />
<ComponentB />
<ComponentC />
</>
);
For first, we create useFormState custom hook as a source for our state.
In the next step, we create useSharedFormState hook who uses useBetween hook inside. That hook can be used in any component who can read or update the shared state!
And the last step is using useSharedFormState in our components.
useBetween is a way to call any hook. But so that the state will not be stored in the React component. For the same hook, the result of the call will be the same. So we can call one hook in different components and work together on one state. When updating the shared state, each component using it will be updated too.
I'll be going straight to hell for this:
// src/hooks/useMessagePipe.ts
import { useReducer } from 'react'
let message = undefined
export default function useMessagePipe(): { message: string | undefined, sendMessage: (filter: string) => void } {
const triggerRender = useReducer((bool) => !bool, true)[1]
function update(term: string) {
message = message.length > 0 ? message : undefined
triggerRender()
}
return { message: message, sendMessage: update }
}
You can then use this in any component anywhere in your applications' component hierarchy to send a message:
// src/components/ExampleInputToHell.jsx:
import useMessagePipe from 'src/hooks/useMessagePipe'
export const ExampleInputToHell() = () => {
const { sendMessage } = useMessagePipe()
return <input onChange={(e) => sendMessage('🔥 Hell-O 😈: ' + e.target.value)} />
}
… and consume the message any component anywhere in your applications' component hierarchy:
// src/components/ExampleOutputInHell.jsx
import useMessagePipe from 'src/hooks/useMessagePipe'
export const ExampleOutputInHell() {
const { message } = useMessagePipe()
return <p>{message}</p>
}
Explanation
let message outside the useMessagePipe-closure holds a global state, that (as far is the theory goes) gets surrounded in it's own module scope
as react's functional component logic will know nothing about that state, triggerRender – a version of a dirty hack that's actually mentioned on the React FAQ – needs to be applied to signal to react that all components consuming this function are asked to re-evaluate (re-render).
Disclaimer
This is a global state, meaning: all components using useMessagePipe see the same message and access the same update function, application-wide. If you want to have a new "channel" between two other components, you need to create another hook referring to another global state holder outside the closure (like message in this example).
If you know any better and have the time and resources, you probably don't want to go down this muddy road to perdition and instead learn how to properly useContext or (an easier way) give useBetween by #Slava Birch a star.
But if you just want a quick and dirty solution to pipe a piece of data between components right now … well this ~10 lines of code made my day for a simple task at hand and worked flawless so far. However my gut feeling says something is going to break if used for important things, hence any additions & theories on the conditions under which it will break are highly welcome.
Either you can set up a parent child relationship then you can pass data to child components as props.
Else, if you want to create interaction between 2 components which are not related to either(parent/child) you can either check out flux or even better redux.
I would say you should go with redux.See Here why
You can build custom React hooks to share a state between components, I made one here. You can use it by downloading use-linked-state.js file.
After importing useStateGateway hook, declare a gateway in parent component and pass it down to your child components
import {useStateGateway} from "use-linked-state";
const myGateway = useStateGateway({partA:null, partB:null});
return (
<>
<ComponentA gateway={myGateway}>
<ComponentB gateway={myGateway}>
<ComponentPost gateWay={myGateway}>
</>
)
Then you have access shared state between those three components by a custom useLinkedState hook
import { useLinkedState } from "use-linked-state";
export default function ComponentA({gateway}){
const [state, setState] = useLinkedState(gateway);
<your logic>
}
In your logic ComponentA and ComponentB would be responsible for their part in shared object {partA:"filled by ComponentA", partB:"filled by componentB"}.
Finally ComponentPost post the result if partA and partB of shared object were valid.
In this way you can compose components and make connection between them to talk to each other.

Props updates in setInterval not reflected when changing component [duplicate]

Why in the following pseudo-code example Child doesn't re-render when Container changes foo.bar?
Container {
handleEvent() {
this.props.foo.bar = 123
},
render() {
return <Child bar={this.props.foo.bar} />
}
Child {
render() {
return <div>{this.props.bar}</div>
}
}
Even if I call forceUpdate() after modifying the value in Container, Child still shows the old value.
Update the child to have the attribute 'key' equal to the name. The component will re-render every time the key changes.
Child {
render() {
return <div key={this.props.bar}>{this.props.bar}</div>
}
}
Because children do not rerender if the props of the parent change, but if its STATE changes :)
What you are showing is this:
https://facebook.github.io/react/tips/communicate-between-components.html
It will pass data from parent to child through props but there is no rerender logic there.
You need to set some state to the parent then rerender the child on parent change state.
This could help.
https://facebook.github.io/react/tips/expose-component-functions.html
I had the same problem.
This is my solution, I'm not sure that is the good practice, tell me if not:
state = {
value: this.props.value
};
componentDidUpdate(prevProps) {
if(prevProps.value !== this.props.value) {
this.setState({value: this.props.value});
}
}
UPD: Now you can do the same thing using React Hooks:
(only if component is a function)
const [value, setValue] = useState(propName);
// This will launch only if propName value has chaged.
useEffect(() => { setValue(propName) }, [propName]);
When create React components from functions and useState.
const [drawerState, setDrawerState] = useState(false);
const toggleDrawer = () => {
// attempting to trigger re-render
setDrawerState(!drawerState);
};
This does not work
<Drawer
drawerState={drawerState}
toggleDrawer={toggleDrawer}
/>
This does work (adding key)
<Drawer
drawerState={drawerState}
key={drawerState}
toggleDrawer={toggleDrawer}
/>
Confirmed, adding a Key works. I went through the docs to try and understand why.
React wants to be efficient when creating child components. It won't render a new component if it's the same as another child, which makes the page load faster.
Adding a Key forces React to render a new component, thus resetting State for that new component.
https://reactjs.org/docs/reconciliation.html#recursing-on-children
Obey immutability
Quite an old question but it's an evergreen problem and it doesn't get better if there are only wrong answers and workarounds.
The reason why the child object is not updating is not a missing key or a missing state, the reason is that you don't obey the principle of immutability.
It is the aim of react to make apps faster and more responsive and easier to maintain and so on but you have to follow some principles. React nodes are only rerendered if it is necessary, i.e. if they have updated. How does react know if a component has updated? Because it state has changed. Now don't mix this up with the setState hook. State is a concept and every component has its state. State is the look or behaviour of the component at a given point in time. If you have a static component you only have one state all the time and don't have to take care of it. If the component has to change somehow its state is changing.
Now react is very descriptive. The state of a component can be derived from some changing information and this information can be stored outside of the component or inside. If the information is stored inside than this is some information the component has to keep track itself and we normally use the hooks like setState to manage this information. If this information is stored outside of our component then it is stored inside of a different component and that one has to keep track of it, its theirs state. Now the other component can pass us their state thru the props.
That means react rerenders if our own managed state changes or if the information coming in via props changes. That is the natural behaviour and you don't have to transfer props data into your own state.
Now comes the very important point: how does react know when information has changed? Easy: is makes an comparison! Whenever you set some state or give react some information it has to consider, it compares the newly given information with the actually stored information and if they are not the same, react will rerender all dependencies of that information.
Not the same in that aspect means a javascript === operator.
Maybe you got the point already.
Let's look at this:
let a = 42;
let b = a;
console.log('is a the same as b?',a === b); // a and b are the same, right? --> true
a += 5; // now let's change a
console.log('is a still the same as b?',a === b); // --> false
We are creating an instance of a value, then create another instance, assign the value of the first instance to the second instance and then change the first instance.
Now let's look at the same flow with objects:
let a = { num: 42};
let b = a;
console.log('is a the same as b?',a === b); // a and b are the same, right? --> true
a.num += 5; // now let's change a
console.log('is a still the same as b?',a === b); // --> true
The difference this time is that an object actually is a pointer to a memory area and with the assertion of b=a you set b to the same pointer as a leading to exactly the same object.
Whatever you do in a can be accesed by your a pointer or your b pointer.
Your line:
this.props.foo.bar = 123
actually updates a value in the memory where "this" is pointing at.
React simply can't recognize such alterations by comparing the object references. You can change the contents of your object a thousand times and the reference will always stay the same and react won't do a rerender of the dependent components.
That is why you have to consider all variables in react as immutable. To make a detectable change you need a different reference and you only get that with a new object. So instead of changing your object you have to copy it to a new one and then you can change some values in it before you hand it over to react.
Look:
let a = {num: 42};
console.log('a looks like', a);
let b = {...a};
console.log('b looks like', b);
console.log('is a the same as b?', a === b); // --> false
The spread operator (the one with the three dots) or the map-function are common ways to copy data to a new object or array.
If you obey immutability all child nodes update with new props data.
According to React philosophy component can't change its props. they should be received from the parent and should be immutable. Only parent can change the props of its children.
nice explanation on state vs props
also, read this thread Why can't I update props in react.js?
You should use setState function. If not, state won't save your change, no matter how you use forceUpdate.
Container {
handleEvent= () => { // use arrow function
//this.props.foo.bar = 123
//You should use setState to set value like this:
this.setState({foo: {bar: 123}});
};
render() {
return <Child bar={this.state.foo.bar} />
}
Child {
render() {
return <div>{this.props.bar}</div>
}
}
}
Your code seems not valid. I can not test this code.
You must have used dynamic component.
In this code snippet we are rendering child component multiple time and also passing key.
If we render a component dynamically multiple time then React doesn't render that component until it's key gets changed.
If we change checked by using setState method. It won't be reflected in Child component until we change its key. We have to save that on child's state and then change it to render child accordingly.
class Parent extends Component {
state = {
checked: true
}
render() {
return (
<div className="parent">
{
[1, 2, 3].map(
n => <div key={n}>
<Child isChecked={this.state.checked} />
</div>
)
}
</div>
);
}
}
My case involved having multiple properties on the props object, and the need to re-render the Child on changing any of them.
The solutions offered above were working, yet adding a key to each an every one of them became tedious and dirty (imagine having 15...). If anyone is facing this - you might find it useful to stringify the props object:
<Child
key={JSON.stringify(props)}
/>
This way every change on each one of the properties on props triggers a re-render of the Child component.
Hope that helped someone.
I have the same issue's re-rendering object props, if the props is an object JSON.stringify(obj) and set it as a key for Functional Components. Setting just an id on key for react hooks doesn't work for me. It's weird that to update the component's you have to include all the object properties on the key and concatenate it there.
function Child(props) {
const [thing, setThing] = useState(props.something)
return (
<>
<div>{thing.a}</div>
<div>{thing.b}</div>
</>
)
}
...
function Caller() {
const thing = [{a: 1, b: 2}, {a: 3, b: 4}]
thing.map(t => (
<Child key={JSON.stringify(t)} something={thing} />
))
}
Now anytime the thing object changes it's values on runtime, Child component will re-render it correctly.
You should probably make the Child as functional component if it does not maintain any state and simply renders the props and then call it from the parent. Alternative to this is that you can use hooks with the functional component (useState) which will cause stateless component to re-render.
Also you should not alter the propas as they are immutable. Maintain state of the component.
Child = ({bar}) => (bar);
export default function DataTable({ col, row }) {
const [datatable, setDatatable] = React.useState({});
useEffect(() => {
setDatatable({
columns: col,
rows: row,
});
/// do any thing else
}, [row]);
return (
<MDBDataTableV5
hover
entriesOptions={[5, 20, 25]}
entries={5}
pagesAmount={4}
data={datatable}
/>
);
}
this example use useEffect to change state when props change.
I was encountering the same problem.
I had a Tooltip component that was receiving showTooltip prop, that I was updating on Parent component based on an if condition, it was getting updated in Parent component but Tooltip component was not rendering.
const Parent = () => {
let showTooltip = false;
if(....){ showTooltip = true; }
return(
<Tooltip showTooltip={showTooltip}></Tooltip>
)
}
The mistake I was doing is to declare showTooltip as a let.
I realized what I was doing wrong I was violating the principles of how rendering works, Replacing it with hooks did the job.
const [showTooltip, setShowTooltip] = React.useState<boolean>(false);
define changed props in mapStateToProps of connect method in child component.
function mapStateToProps(state) {
return {
chanelList: state.messaging.chanelList,
};
}
export default connect(mapStateToProps)(ChannelItem);
In my case, channelList's channel is updated so I added chanelList in mapStateToProps
In my case I was updating a loading state that was passed down to a component. Within the Button the props.loading was coming through as expected (switching from false to true) but the ternary that showed a spinner wasn't updating.
I tried adding a key, adding a state that updated with useEffect() etc but none of the other answers worked.
What worked for me was changing this:
setLoading(true);
handleOtherCPUHeavyCode();
To this:
setLoading(true);
setTimeout(() => { handleOtherCPUHeavyCode() }, 1)
I assume it's because the process in handleOtherCPUHeavyCode is pretty heavy and intensive so the app freezes for a second or so. Adding the 1ms timeout allows the loading boolean to update and then the heavy code function can do it's work.
You can use componentWillReceiveProps:
componentWillReceiveProps({bar}) {
this.setState({...this.state, bar})
}
Credit to Josh Lunsford
Considering the rendering limitations with props and the gains we have with states, if you use reaction hooks, there are a few tricks you can use. For example, you can convert props to state manually using useEffect. It probably shouldn't be the best practice, but it helps in theses cases.
import { isEqual } from 'lodash';
import { useEffect, useState } from 'react';
export const MyComponent = (props: { users: [] }) => {
const [usersState, setUsersState] = useState([]);
useEffect(() => {
if (!isEqual(props.users, usersState)) {
setUsersState(props.users);
}
}, [props.users]);
<OtherComponent users={usersState} />;
};

Clearing specific redux state before rendering component

I have the following "Buy" button for a shopping cart.
I also have a component called Tooltip, which will display itself for error/success messages. It uses the button's width to determine it's centre point. Hence, I use a `ref since I need to access it's physical size within the DOM. I've read that it's bad news to use a ref attribute, but I'm not sure how else to go about doing the positioning of a child component that is based off the physical DOM. But that's another question... ;)
I am persisting the app's state in localStorage. As seen here:
https://egghead.io/lessons/javascript-redux-persisting-the-state-to-the-local-storage
The issue I'm running into is that I have to clear the state's success property before rendering. Otherwise, if I have a success message in the state, on the initial render() the Tooltip will attempt to render as well. This won't be possible since the button it relies on is not yet in the DOM.
I thought that clearing the success state via Redux action in componentWillMount would clear up the success state and therefore clear up the issue, but it appears that the render() method doesn't recognize that the state has been changed and will still show the old value in console.log().
My work-around is to check if the button exists as well as the success message: showSuccessTooltip && this.addBtn
Why does render() not recognize the componentWillMount() state change?
Here is the ProductBuyBtn.js class:
import React, { Component } from 'react';
import { connect } from 'react-redux'
// Components
import Tooltip from './../utils/Tooltip'
// CSS
import './../../css/button.css'
// State
import { addToCart, clearSuccess } from './../../store/actions/cart'
class ProductBuyBtn extends Component {
componentWillMount(){
this.props.clearSuccess()
}
addToCart(){
this.props.addToCart(process.env.REACT_APP_SITE_KEY, this.props.product.id, this.props.quantity)
}
render() {
let showErrorTooltip = this.props.error !== undefined
let showSuccessTooltip = this.props.success !== undefined
console.log(this.props.success)
return (
<div className="btn_container">
<button className="btn buy_btn" ref={(addBtn) => this.addBtn = addBtn } onClick={() => this.addToCart()}>Add</button>
{showErrorTooltip && this.addBtn &&
<Tooltip parent={this.addBtn} type={'dialog--error'} messageObjects={this.props.error} />
}
{showSuccessTooltip && this.addBtn &&
<Tooltip parent={this.addBtn} type={'dialog--success'} messageObjects={{ success: this.props.success }} />
}
</div>
);
}
}
function mapStateToProps(state){
return {
inProcess: state.cart.inProcess,
error: state.cart.error,
success: state.cart.success
}
}
const mapDispatchToProps = (dispatch) => {
return {
addToCart: (siteKey, product_id, quantity) => dispatch(addToCart(siteKey, product_id, quantity)),
clearSuccess: () => dispatch(clearSuccess())
}
}
export default connect(mapStateToProps, mapDispatchToProps)(ProductBuyBtn)
Well, it seems to be a known problem that's easy to get into (harder to get out of, especially in a nice / non-hacky way. See this super-long thread).
The problem is that dispatching an action in componentWillMount that (eventually) changes the props going in to a component does not guarantee that the action has taken place before the first render.
So basically the render() doesn't wait for your dispatched action to take effect, it renders once (with the old props), then the action takes effect and changes the props and then the component re-renders with the new props.
So you either have to do what you already do, or use the components internal state to keep track of whether it's the first render or not, something like this comment. There are more suggestions outlined, but I can't list them all.

React: why child component doesn't update when prop changes

Why in the following pseudo-code example Child doesn't re-render when Container changes foo.bar?
Container {
handleEvent() {
this.props.foo.bar = 123
},
render() {
return <Child bar={this.props.foo.bar} />
}
Child {
render() {
return <div>{this.props.bar}</div>
}
}
Even if I call forceUpdate() after modifying the value in Container, Child still shows the old value.
Update the child to have the attribute 'key' equal to the name. The component will re-render every time the key changes.
Child {
render() {
return <div key={this.props.bar}>{this.props.bar}</div>
}
}
Because children do not rerender if the props of the parent change, but if its STATE changes :)
What you are showing is this:
https://facebook.github.io/react/tips/communicate-between-components.html
It will pass data from parent to child through props but there is no rerender logic there.
You need to set some state to the parent then rerender the child on parent change state.
This could help.
https://facebook.github.io/react/tips/expose-component-functions.html
I had the same problem.
This is my solution, I'm not sure that is the good practice, tell me if not:
state = {
value: this.props.value
};
componentDidUpdate(prevProps) {
if(prevProps.value !== this.props.value) {
this.setState({value: this.props.value});
}
}
UPD: Now you can do the same thing using React Hooks:
(only if component is a function)
const [value, setValue] = useState(propName);
// This will launch only if propName value has chaged.
useEffect(() => { setValue(propName) }, [propName]);
When create React components from functions and useState.
const [drawerState, setDrawerState] = useState(false);
const toggleDrawer = () => {
// attempting to trigger re-render
setDrawerState(!drawerState);
};
This does not work
<Drawer
drawerState={drawerState}
toggleDrawer={toggleDrawer}
/>
This does work (adding key)
<Drawer
drawerState={drawerState}
key={drawerState}
toggleDrawer={toggleDrawer}
/>
Obey immutability
Quite an old question but it's an evergreen problem and it doesn't get better if there are only wrong answers and workarounds.
The reason why the child object is not updating is not a missing key or a missing state, the reason is that you don't obey the principle of immutability.
It is the aim of react to make apps faster and more responsive and easier to maintain and so on but you have to follow some principles. React nodes are only rerendered if it is necessary, i.e. if they have updated. How does react know if a component has updated? Because it state has changed. Now don't mix this up with the setState hook. State is a concept and every component has its state. State is the look or behaviour of the component at a given point in time. If you have a static component you only have one state all the time and don't have to take care of it. If the component has to change somehow its state is changing.
Now react is very descriptive. The state of a component can be derived from some changing information and this information can be stored outside of the component or inside. If the information is stored inside than this is some information the component has to keep track itself and we normally use the hooks like setState to manage this information. If this information is stored outside of our component then it is stored inside of a different component and that one has to keep track of it, its theirs state. Now the other component can pass us their state thru the props.
That means react rerenders if our own managed state changes or if the information coming in via props changes. That is the natural behaviour and you don't have to transfer props data into your own state.
Now comes the very important point: how does react know when information has changed? Easy: is makes an comparison! Whenever you set some state or give react some information it has to consider, it compares the newly given information with the actually stored information and if they are not the same, react will rerender all dependencies of that information.
Not the same in that aspect means a javascript === operator.
Maybe you got the point already.
Let's look at this:
let a = 42;
let b = a;
console.log('is a the same as b?',a === b); // a and b are the same, right? --> true
a += 5; // now let's change a
console.log('is a still the same as b?',a === b); // --> false
We are creating an instance of a value, then create another instance, assign the value of the first instance to the second instance and then change the first instance.
Now let's look at the same flow with objects:
let a = { num: 42};
let b = a;
console.log('is a the same as b?',a === b); // a and b are the same, right? --> true
a.num += 5; // now let's change a
console.log('is a still the same as b?',a === b); // --> true
The difference this time is that an object actually is a pointer to a memory area and with the assertion of b=a you set b to the same pointer as a leading to exactly the same object.
Whatever you do in a can be accesed by your a pointer or your b pointer.
Your line:
this.props.foo.bar = 123
actually updates a value in the memory where "this" is pointing at.
React simply can't recognize such alterations by comparing the object references. You can change the contents of your object a thousand times and the reference will always stay the same and react won't do a rerender of the dependent components.
That is why you have to consider all variables in react as immutable. To make a detectable change you need a different reference and you only get that with a new object. So instead of changing your object you have to copy it to a new one and then you can change some values in it before you hand it over to react.
Look:
let a = {num: 42};
console.log('a looks like', a);
let b = {...a};
console.log('b looks like', b);
console.log('is a the same as b?', a === b); // --> false
The spread operator (the one with the three dots) or the map-function are common ways to copy data to a new object or array.
If you obey immutability all child nodes update with new props data.
Confirmed, adding a Key works. I went through the docs to try and understand why.
React wants to be efficient when creating child components. It won't render a new component if it's the same as another child, which makes the page load faster.
Adding a Key forces React to render a new component, thus resetting State for that new component.
https://reactjs.org/docs/reconciliation.html#recursing-on-children
According to React philosophy component can't change its props. they should be received from the parent and should be immutable. Only parent can change the props of its children.
nice explanation on state vs props
also, read this thread Why can't I update props in react.js?
You should use setState function. If not, state won't save your change, no matter how you use forceUpdate.
Container {
handleEvent= () => { // use arrow function
//this.props.foo.bar = 123
//You should use setState to set value like this:
this.setState({foo: {bar: 123}});
};
render() {
return <Child bar={this.state.foo.bar} />
}
Child {
render() {
return <div>{this.props.bar}</div>
}
}
}
Your code seems not valid. I can not test this code.
You must have used dynamic component.
In this code snippet we are rendering child component multiple time and also passing key.
If we render a component dynamically multiple time then React doesn't render that component until it's key gets changed.
If we change checked by using setState method. It won't be reflected in Child component until we change its key. We have to save that on child's state and then change it to render child accordingly.
class Parent extends Component {
state = {
checked: true
}
render() {
return (
<div className="parent">
{
[1, 2, 3].map(
n => <div key={n}>
<Child isChecked={this.state.checked} />
</div>
)
}
</div>
);
}
}
My case involved having multiple properties on the props object, and the need to re-render the Child on changing any of them.
The solutions offered above were working, yet adding a key to each an every one of them became tedious and dirty (imagine having 15...). If anyone is facing this - you might find it useful to stringify the props object:
<Child
key={JSON.stringify(props)}
/>
This way every change on each one of the properties on props triggers a re-render of the Child component.
Hope that helped someone.
I have the same issue's re-rendering object props, if the props is an object JSON.stringify(obj) and set it as a key for Functional Components. Setting just an id on key for react hooks doesn't work for me. It's weird that to update the component's you have to include all the object properties on the key and concatenate it there.
function Child(props) {
const [thing, setThing] = useState(props.something)
return (
<>
<div>{thing.a}</div>
<div>{thing.b}</div>
</>
)
}
...
function Caller() {
const thing = [{a: 1, b: 2}, {a: 3, b: 4}]
thing.map(t => (
<Child key={JSON.stringify(t)} something={thing} />
))
}
Now anytime the thing object changes it's values on runtime, Child component will re-render it correctly.
You should probably make the Child as functional component if it does not maintain any state and simply renders the props and then call it from the parent. Alternative to this is that you can use hooks with the functional component (useState) which will cause stateless component to re-render.
Also you should not alter the propas as they are immutable. Maintain state of the component.
Child = ({bar}) => (bar);
export default function DataTable({ col, row }) {
const [datatable, setDatatable] = React.useState({});
useEffect(() => {
setDatatable({
columns: col,
rows: row,
});
/// do any thing else
}, [row]);
return (
<MDBDataTableV5
hover
entriesOptions={[5, 20, 25]}
entries={5}
pagesAmount={4}
data={datatable}
/>
);
}
this example use useEffect to change state when props change.
I was encountering the same problem.
I had a Tooltip component that was receiving showTooltip prop, that I was updating on Parent component based on an if condition, it was getting updated in Parent component but Tooltip component was not rendering.
const Parent = () => {
let showTooltip = false;
if(....){ showTooltip = true; }
return(
<Tooltip showTooltip={showTooltip}></Tooltip>
)
}
The mistake I was doing is to declare showTooltip as a let.
I realized what I was doing wrong I was violating the principles of how rendering works, Replacing it with hooks did the job.
const [showTooltip, setShowTooltip] = React.useState<boolean>(false);
define changed props in mapStateToProps of connect method in child component.
function mapStateToProps(state) {
return {
chanelList: state.messaging.chanelList,
};
}
export default connect(mapStateToProps)(ChannelItem);
In my case, channelList's channel is updated so I added chanelList in mapStateToProps
In my case I was updating a loading state that was passed down to a component. Within the Button the props.loading was coming through as expected (switching from false to true) but the ternary that showed a spinner wasn't updating.
I tried adding a key, adding a state that updated with useEffect() etc but none of the other answers worked.
What worked for me was changing this:
setLoading(true);
handleOtherCPUHeavyCode();
To this:
setLoading(true);
setTimeout(() => { handleOtherCPUHeavyCode() }, 1)
I assume it's because the process in handleOtherCPUHeavyCode is pretty heavy and intensive so the app freezes for a second or so. Adding the 1ms timeout allows the loading boolean to update and then the heavy code function can do it's work.
You can use componentWillReceiveProps:
componentWillReceiveProps({bar}) {
this.setState({...this.state, bar})
}
Credit to Josh Lunsford
Considering the rendering limitations with props and the gains we have with states, if you use reaction hooks, there are a few tricks you can use. For example, you can convert props to state manually using useEffect. It probably shouldn't be the best practice, but it helps in theses cases.
import { isEqual } from 'lodash';
import { useEffect, useState } from 'react';
export const MyComponent = (props: { users: [] }) => {
const [usersState, setUsersState] = useState([]);
useEffect(() => {
if (!isEqual(props.users, usersState)) {
setUsersState(props.users);
}
}, [props.users]);
<OtherComponent users={usersState} />;
};

How to make a shared state between two react components?

I have 2 react components that need to share a state, react-router shows component A, which takes some inputs and adds it to its state, after the state has been successfully updated, I want to redirect to component B, where the user adds some more inputs and updates the same state as component A to build an object with inputs from A and B before I submit a post request to my api to save the data from both component A and B. How can I accomplish this, is there a way to use react-router, or do I have to set up a parent/child relationship between the components?
The dependency type between the components will define the best approach.
For instance, redux is a great option if you plan to have a central store. However other approaches are possible:
Parent to Child
Props
Instance Methods
Child to Parent
Callback Functions
Event Bubbling
Sibling to Sibling
Parent Component
Any to Any
Observer Pattern
Global Variables
Context
Please find more detailed information about each of the approaches here
What you want is to implement some object that stores your state, that can be modified using callback functions. You can then pass these functions to your React components.
For instance, you could create a store:
function Store(initialState = {}) {
this.state = initialState;
}
Store.prototype.mergeState = function(partialState) {
Object.assign(this.state, partialState);
};
var myStore = new Store();
ReactDOM.render(
<FirstComponent mergeState={myStore.mergeState.bind(myStore)} />,
firstElement
);
ReactDOM.render(
<SecondComponent mergeState={myStore.mergeState.bind(myStore)} />,
secondElement
);
Now, both the FirstComponent and SecondComponent instances can call this.props.mergeState({ . . .}) to assign state to the same store.
I leave Store.prototype.getState as an exercise for the reader.
Note that you can always pass the store (myStore) itself to the components; it just feels less react-y to do so.
Here is some more documentation that might be of interest:
React Docs: "Communicate Between Components"
For communication between two components that don't have a
parent-child relationship, you can set up your own global event
system. Subscribe to events in componentDidMount(), unsubscribe in
componentWillUnmount(), and call setState() when you receive an event.
Flux pattern is one of the possible ways to arrange this.
The easiest way to use a shared state between several components without rewriting your application's code to some state management system is use-between hook.
Try this example in codesandbox
import React, { useState } from "react";
import { useBetween } from "use-between";
// Make a custom hook with your future shared state
const useFormState = () => {
const [username, setUsername] = useState("");
const [email, setEmail] = useState("");
return {
username, setUsername, email, setEmail
};
};
// Make a custom hook for sharing your form state between any components
const useSharedFormState = () => useBetween(useFormState);
const ComponentA = () => {
// Use the shared hook!
const { username, setUsername } = useSharedFormState();
return (
<p>
Username: <input value={username} onChange={(ev) => setUsername(ev.target.value)} />
</p>
);
};
const ComponentB = () => {
// Use the shared hook!
const { email, setEmail } = useSharedFormState();
return (
<p>
Email: <input value={email} onChange={(ev) => setEmail(ev.target.value)} />
</p>
);
};
const ComponentC = () => {
// Use shared hook!
const { email, username } = useSharedFormState();
return (
<p>
Username: {username} <br />
Email: {email}
</p>
);
};
export const App = () => (
<>
<ComponentA />
<ComponentB />
<ComponentC />
</>
);
For first, we create useFormState custom hook as a source for our state.
In the next step, we create useSharedFormState hook who uses useBetween hook inside. That hook can be used in any component who can read or update the shared state!
And the last step is using useSharedFormState in our components.
useBetween is a way to call any hook. But so that the state will not be stored in the React component. For the same hook, the result of the call will be the same. So we can call one hook in different components and work together on one state. When updating the shared state, each component using it will be updated too.
I'll be going straight to hell for this:
// src/hooks/useMessagePipe.ts
import { useReducer } from 'react'
let message = undefined
export default function useMessagePipe(): { message: string | undefined, sendMessage: (filter: string) => void } {
const triggerRender = useReducer((bool) => !bool, true)[1]
function update(term: string) {
message = message.length > 0 ? message : undefined
triggerRender()
}
return { message: message, sendMessage: update }
}
You can then use this in any component anywhere in your applications' component hierarchy to send a message:
// src/components/ExampleInputToHell.jsx:
import useMessagePipe from 'src/hooks/useMessagePipe'
export const ExampleInputToHell() = () => {
const { sendMessage } = useMessagePipe()
return <input onChange={(e) => sendMessage('🔥 Hell-O 😈: ' + e.target.value)} />
}
… and consume the message any component anywhere in your applications' component hierarchy:
// src/components/ExampleOutputInHell.jsx
import useMessagePipe from 'src/hooks/useMessagePipe'
export const ExampleOutputInHell() {
const { message } = useMessagePipe()
return <p>{message}</p>
}
Explanation
let message outside the useMessagePipe-closure holds a global state, that (as far is the theory goes) gets surrounded in it's own module scope
as react's functional component logic will know nothing about that state, triggerRender – a version of a dirty hack that's actually mentioned on the React FAQ – needs to be applied to signal to react that all components consuming this function are asked to re-evaluate (re-render).
Disclaimer
This is a global state, meaning: all components using useMessagePipe see the same message and access the same update function, application-wide. If you want to have a new "channel" between two other components, you need to create another hook referring to another global state holder outside the closure (like message in this example).
If you know any better and have the time and resources, you probably don't want to go down this muddy road to perdition and instead learn how to properly useContext or (an easier way) give useBetween by #Slava Birch a star.
But if you just want a quick and dirty solution to pipe a piece of data between components right now … well this ~10 lines of code made my day for a simple task at hand and worked flawless so far. However my gut feeling says something is going to break if used for important things, hence any additions & theories on the conditions under which it will break are highly welcome.
Either you can set up a parent child relationship then you can pass data to child components as props.
Else, if you want to create interaction between 2 components which are not related to either(parent/child) you can either check out flux or even better redux.
I would say you should go with redux.See Here why
You can build custom React hooks to share a state between components, I made one here. You can use it by downloading use-linked-state.js file.
After importing useStateGateway hook, declare a gateway in parent component and pass it down to your child components
import {useStateGateway} from "use-linked-state";
const myGateway = useStateGateway({partA:null, partB:null});
return (
<>
<ComponentA gateway={myGateway}>
<ComponentB gateway={myGateway}>
<ComponentPost gateWay={myGateway}>
</>
)
Then you have access shared state between those three components by a custom useLinkedState hook
import { useLinkedState } from "use-linked-state";
export default function ComponentA({gateway}){
const [state, setState] = useLinkedState(gateway);
<your logic>
}
In your logic ComponentA and ComponentB would be responsible for their part in shared object {partA:"filled by ComponentA", partB:"filled by componentB"}.
Finally ComponentPost post the result if partA and partB of shared object were valid.
In this way you can compose components and make connection between them to talk to each other.

Categories

Resources