is it possible to add reducers after the store has been created?
example :
let's say I have this react app
//App.jsx
const reducers = {foo : []}
const store = createStore(combineReducers(reducers));
const App = () => <Provider store={store}><Test/></Provider>
//Test.jsx
class Test extends Component {
componentDidMount() {
//here I want to add new reducer to the store
//I will handle duplication problem
//I'm looking for something like this
store.appendReducer('REDUCER_KEY' , someReducer);
}
...
}
export default connect()(Test)
I'm wondering if this possible, if so how to implement this.
I have an idea in mind, but it's not perfect. I can add a global callback function and listen for any call at the root component, then recreate the store when any call happens. the problem with this it will cause the whole component tree to be rerendered at any reducer addition.
yes it's possible some thing like that
const newRootReducer = combineReducers({
existingSlice: existingSliceReducer,
newSlice: newSliceReducer
})
store.replaceReducer(newRootReducer)
you can check the DOC for more info redux DOC
Related
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.
I am refractoring an app I've build using React.js. I am exporting a variable from the global scope of Spotify.js and importing it in two other files App.js and Button.js.
After calling a function from Spotify.js that sotres a new value to the variable, It's new value is exported to 'Button.js' but stays an empty string in 'App.js'.
Your help would be appriciated :)
export let userAccessToken = '';
export const Spotify = {
...
getUserAccessToken (){
//stores a new string to userAccessToken.
}
}
import {userAccessToken, Spotify} from '../../util/Spotify';
export class App extends React.Component {
//a conditional rendering happens depending on if(!userAccessToken)
}
import {userAccessToken, Spotify} from '../../util/Spotify'
export class Button extends React.Component {
componentDidMount() {
if (!userAccessToken) {
console.log(`Button's UAT before calling the fn: ${userAccessToken}`)
Spotify.getUserAccessToken();
console.log(`Button's UAT after calling the fn: ${userAccessToken}`);
}
}
...
}
This is not how you share data between react components.
Use react context or pass props between components
You could use react context to share data or simply pass it as props (if the components are closely related in the component tree)
The only thing I can recommend is to export the userAccessToken, something like this, but you can't change its value outside the module
export const Spotify = {
...
getUserAccessToken (){
//stores a new string to userAccessToken.
}
}
...
}
const accessToken = Spotify.getUserAccessToken();
export const userAccessToken = accessToken;
If you got to read this question I solved it.
Turns out I should have called my method Spotify.getUserAccessToken() from App.js using the react lifecycle method componentDidMount().
The export and import methods are like snapshots of the module and therefore when I exported the variable userAccessToke from Spotify.js before calling the my method I imported an empty string and it did not update in all files.
Thanks JΓΈrgen and joseluismurillorios for your support and time spent answering :)
Sorry if the question looks stupid, I'm approaching at redux/redux.
I'm not able to understand if I'm doing something wrong :(
If I have a component that is attached, let's say, to the store section called "user'
Class UserComponent extends React.Component {
[...]
}
const UserWithStore = connect(mapStateToProps)(TokenComponent);
where map state to props is :
const mapStateToProps = state => {
return {
user: state['user']
}
};
Whenever from the user component I trigger an action that updates the store section like:
this.props.dispatch(action)
Basically, the component goes in a loop because obviously, I'm updating the same portion of the store.
Is it anti-pattern? I have to check always previous props vs current props or I should trigger actions from a different component?
Thank you for helping me
In my App I use several Container components. In each Container, there are Buttons.
Depending on the state of the App, the Buttons are clickable (or not). Whether a Button is disabled or not, is managed in the local state of each Container.
The results and state of the App can be saved and loaded.
And here comes the problem:
When I save (or load) the App, its rather hard to "extract" the state of the Buttons from each Container. Saving in the global state (Redux)is rather easy.
But how can I save the local state from each Container and how can I feed it back to each Container?
Reading the local state is managed through a parent Component which calls methods from a child Component. I am aware that this is an antipattern, but it works.
export class SomeConmponent {
....
onClickSaveProjecthandler(event) {
const localStateProjectSettings = this.childProjectSettings.getLocalState();
const localStateLayerFilter = this.childLayerFilter.getLocalState();
return {
"ProjectSettings": localStateProjectSettings,
"Filter": localFilter
};
}
render() {
return(
<ProjectSettingsContainer onRef={ref => (this.childProjectSettings = ref)}/>
)
}
}
Any better suggestions?
As you already mentioned, using redux to have a single point of truth is a great ideia. And to "feed" the state back to containers, you have to map state and props to your components.
This is a container example brought from the oficial doc:
import { connect } from 'react-redux'
import { setVisibilityFilter } from '../actions'
import Link from '../components/Link'
const mapStateToProps = (state, ownProps) => {
return {
active: ownProps.filter === state.visibilityFilter
}
}
const mapDispatchToProps = (dispatch, ownProps) => {
return {
onClick: () => {
dispatch(setVisibilityFilter(ownProps.filter))
}
}
}
const FilterLink = connect(
mapStateToProps,
mapDispatchToProps
)(Link)
export default FilterLink
The connect does all the magic.
Sounds like you could use Redux and you somehow miscomprehended its architecture as being synonymous with global state only. One of the reasons Redux was made was to address the issue of saving states of multiple independent components.
However, here's an entirely different take on the answer: why not use a global state serializer and connect each component to it? If you don't like the idea of referring to a global variable, a better alternative would be to create some sort of dependency injection (DI) framework that works with React. I've created a library a while back, called AntidoteJS, that does exactly this. You don't need to use it, but it shows how you how it can be done. Just take a look at the source.
Here is something quick and dirty. I haven't tested it, but it shows the basic idea:
import { inject } from "antidotejs"
export class MyComponent extends React.Component {
#inject("StateSerializer")
serializer
constuctor(props, children) {
super(props, children);
this.serializer.load(props.id);
}
setState(newState) {
super.setState(newState);
this.serializer.save(newState);
}
}
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.