Redux and React - Keeping component props in sync with state via #connect - javascript

I have an application set up using React and Redux such that I have a root application component wrapped around a provider (which is passing the store down so that my child components can access state and dispatch) like so:
<Provider store={Store}>
<RootComponent />
</Provider>
My RootComponent essentially uses a JSON file to render Template components that get passed a key and title (
<Application children={children} />
Within one of the children, I've hooked up the state of the application to the Component using the #connect decorator and I'm firing off a dispatcher upon construction to update my 'content' prop like so:
#connect(state => ({content: state.content}))
export default class DynamicText extends Component {
constructor(props) {
super(props);
var Content = require(`./jsonContent`);
props.dispatch(loadContent(Content[props.title].elements));
}
**actions.js**
export function loadContent(content) {
return {
type: 'LOAD_CONTENT',
content
};
}
**reducers.js**
const initialState = {
route: null
};
export default function routeReducer(state = initialState, action) {
switch (action.type) {
case LOAD_CONTENT:
return Object.assign({}, state, {
content: action.content
});
default:
return state;
}
The issue I'm having is that when booting up my application, the 'content' prop is undefined - upon page refresh however, it populates with the updated 'content' data from the JSON file. I think the issue may be occurring because initially the state.content referenced in my #connect decorator is undefined, however I was hoping that the immediate dispatch on the constructor would solve this issue.
Does anyone have experience or recommendations on how I can get this working without a refresh? Is #connect really the best option here or are there alternatives?

You need to hydrate your store before passing it down to Provider providing an initial state to it so that when your app "boots" your store already has the data available.
Using connect is the right way to go after that.
If you're running a server side rendered application you can put your json contents in a property attached to window on first request then empty it after store has been hydrated.
Something along the lines of window.__BOOTSTRAP__.
store = createStore(appReducers, { content: yourJsonFileContents });
<Provider store={store}>
<RootComponent />
</Provider>

Related

React - Wrapping all components in redux provider with store injection, Multi page app

I have created an hoc which looks like
const withStore = Component => {
const storeWrapper = props => (
<ReduxProvider store={store}>
<Component {...props } />
</ReduxProvider>
);
return storeWrapper;
};
Now all my separate components are wrapped by this(its multi page application) so that they can connect to same store, but that's not the case, one component is getting connected, changing with the incoming changes in props. But other components are totally disconnected not showing any change with data being changed in store.
Has anyone tried this approach earlier?
An example - if I have componentA and componentB, wrapped with this hoc.
Any store change from dispatch from componentA is getting reflected by componentA. Same for componentB. Main problem is when dispatch is from componentA and it is not getting read of componentB. vice-versa
As mentioned in #orel's comment, I resolved it by storing store in global variable.
In store.js file, to keep single instance everywhere, I added a condition
if (!window.store) {
window.store = store;
}
It works like charm now!!

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.

React Reuse Fetch JSON data throughout components

Hi there I am newbie to React and learning about reusable functions throughout my project.
I would like to fetch my JSON data but not have to call it everytime in my component.
App.js
import React, { Component } from 'react';
import { Route, NavLink, HashRouter } from "react-router-dom";
import logo from '../assets/logo.png';
import './app.css';
import About from "../about/about";
import Services from "../services/services";
import Testimonials from "../testimonials/testimonials";
import Contact from "../contact/contact";
class App extends Component {
constructor(props) {
super(props);
this.state = {
items : []
};
}
componentDidMount(){
this.getItems();
}
getItems(){
fetch('./data/data_arr.js')
.then(results => results.json())
.then(results => this.setState({'items': results}));
}
render() {
return (
<HashRouter>
<div className="container">
<div className="header">
<div className="App-logo"><NavLink exact to="/"><img src={logo} alt="logo" /></NavLink></div>
<nav className="Nav-Desktop">
{this.state.items.map((item, index) => (
<div key={index}>
{
item.links.map((link, i) => (
<NavLink key={i} exact to={link.url}>{link.text}</NavLink>
))}
</div>
))}
{
this.state.items.map((item, index) => {
return <div key={index}></i><strong> {item.mainContact.phone}</strong></div>
})
}
</nav>
</div>
<main className="content">
<Route exact path="/" component={About}/>
<Route path="/services" component={Services}/>
<Route path="/testimonials" component={Testimonials}/>
<Route path="/contact" component={Contact}/>
</main>
{this.state.items.map((item, index) => {
return <footer key={index}>Β© Copyright {item.title} {(new Date().getFullYear())}</footer>
})
}
</div>
</HashRouter>
);
}
}
export default App;
I am successfully mapping my data and displaying it, but I have other files that include this snippet
constructor(props) {
super(props);
this.state = {
items : []
};
}
componentDidMount(){
this.getItems();
}
getItems(){
fetch('./data/data_arr.js')
.then(results => results.json())
.then(results => this.setState({'items': results}));
}
I have tried exporting the getItems() like so in a helper.js file and importing the file import { getItems } from '../helpers/helpers'; however the code did not work properly and got stuck at Unhandled Rejection (TypeError): Cannot read property 'setState' of undefined
export function getItems() {
fetch('./data/data_arr.js')
.then(results => results.json())
.then(results => this.setState({'items': results}));
}
If anyone can give me pointers as to the error / right way to go about this that would be helpful. Cheers
Two things you need to know when you want to reuse the data instead of calling fetch again and again
Do fetch call in top most component i.e., parent component and pass down data to all the children, children to children components but, do remember this will be hectic for you. This approach is good when you are building small application which will be like max to max 50 components. But when your application grows big this is not a recommended way of reusing the data across components.
Use Redux state management library for data reusability across components. This acts like a centralised store for your application. This is used mostly in every React app these days. With Redux you can make an action call in parent component and that action will actually fetch the data and pass it to the reducer. So now reducer will set the data in Redux store. Now the data is accessible in every component by getting the data from Redux store using state.get. So you can call redux action like this.props.getItems(); wherever you need the data in the component and the component mapStateToProps will make that data available to your component as props
How to get the data from Redux store?
Define a function mapStateToProps in component and get the data from Redux store using state.get and return it in the function. Pass the mapStateToProps to connect method. You will be connecting your component to Redux HOC component called connect. This connect method accepts actions and Redux state and make them available to the component as props.
Regarding your issue
Unhandled Rejection (TypeError): Cannot read property 'setState' of undefined
The reason you get this issue because this isn’t available inside the exported getItems function.
What you need to do to fix the issue is pass this to getItems function as a parameter
Note: Here this is a current context
import { getItems } from '../helpers/helpers';
componentDidMount(){
getItems(this);
}
helpers.js:
the below function is treated as a normal JavaScript function. This function no wr belongs the component to bind it in component constructor. But in order to play with the state of the component you need to pass the component context this. But this is old way, now a days we all use Redux to avoid these old concepts
export function getItems(this) {
fetch('./data/data_arr.js')
.then(results => results.json())
.then(results => this.setState({'items': results}));
}
This way you can reuse this function and do the setState.
But do remember these solutions will make complex when your application grows big in future. So I would argue you to go with Redux state management library from now to avoid hectic migration in future :)
Excuse me if there are any typo errors because I am answering from my mobile.
You need to pass the context to your helper function. Considering you're still importing the function from your helper, on your componentDidMount, it can be like:
componentDidMount(){
getItems.call(this);
}
So good job with the fetching, there are a lot of paths you can take with sharing data across components, the two main ones you can look into are:
Parent state holds the data, passes it through props
A Store (Mobx, Redux) holds the data, and you inject it into your components as needed
I'm not going to give you the tutorial on how to do them all but I'm assuming the first option will be best for you right now.
If your components are using the same data, they should theoretically have the same parent component in a structure similar to this:
Page Component (Overarching component, lays out the page structure)
Header
Component 1
Component 2
Footer
So you'll do this call on the mount of Page component and pass this.state.items as a prop to the other components that need it... Stores have a similar process they just save the double handling of props :)
Since you're accessing/modifying state in your getItems() method, you should bind it in your constructor like this:
constructor() {
super(props);
this.state = {
items : []
};
this.getItems = this.getItems.bind(this);
}
or you can simply declare it with arrow function so you don't have to bind it:
getItems = () => {
// some codes here
}
Another thing, the only ones who can access/modify the state of a compoenent is its own methods. If you want to use it outside of itself, import those components inside your component as children then pass your getItems() method as prop.
render() {
return (
<HashRouter>
<div className="container">
// some other codes
<ChildComponent getItems={this.getItems} />
</div>
</HashRouter>
);
}

React Router, Redux and async API call

When the user clicks in a link, React Router will display a component. The data this component will show came from an endpoint, so I'm wondering what's the best practice.
I've created a function called fetchData which uses fetch to perform a GET in an endpoint and then returns a promise. Once this promise is resolved, I would like to dispatch a Redux action to update the state.
I managed to do this with redux-thunk, but I would like to implement this without adding more libraries.
I'm trying to follow the 'container/presentational' idea and I'm using stateless functional components in React.
In a general overview, this is what I'm doing:
index.js
ReactDOM.render(
<Provider store={store}>
<App />
</Provider>,
document.getElementById('root')
)
app.js
const App = () => (
<div>
<BrowserRouter>
<div>
<Header />
<Switch>
<Route exact path="/" component={Main} />
<Route exact path="/data-list" component={DataListContainer} />
</Switch>
</div>
</BrowserRouter>
</div>
)
dataListContainer.js
const mapStateToProps = (state) => {
return { data: state.data }
}
const mapDispatchToProps = (dispatch) => {
return { }
}
const DataListContainer = connect(
mapStateToProps,
mapDispatchToProps
)(DataList)
dataList.js
const DataList = (props) => {
const rows = props.data.map(data => {
return <Data data={data} />
})
return (
<div>
{rows}
</div>
)
}
And the Data component simply displays the data. I'm wondering where I should add the call to the function fetchData and where I should solve the returned promise. I imagine I will need to dispatch an action after the promise is resolved, but not sure where is the best place to do this.
Other question is: I would like to fetch the data only once, I mean, only when the user clicks the /data-list link. If it comes back to main page and then goes again to data-list, I would like to not call the endpoint again. Is there any call once feature hidden in React Route implementation?
Change dataListContainer.js to be a stateful React component, this is ok because it's the container! Don't take the code for exact, it's to just to give an idea.
import { fetchData, DataList, store } '......'
class DataListContainer extends React.Component {
componentDidMount() {
if (!this.props.data) {
store.dispatch(fetchData())
}
}
render() {
return (<DataList data={this.props.data}/>);
}
}
const mapStateToProps = (state) => {
return { data: state.data }
}
export default connect(
mapStateToProps
)(DataListContainer)
I don't think this is possible in the routes and views. When you switch pages with React Router, the component unmounts. All the data of the component is also cleared. (Believe me, this is something you want. Otherwise you might get some serieus memory issues that let your browser crash). Also, your view is only responsible for displaying stuff and should not do things with the data it receives.
Take a look for some implementation in your store. For example store the received data from the API in the store object. The next time someone is calling the fetchData function in the store, serve the stored data instead of make a new request. The store never unmounts so it can hold data in memory. Keep in mind that the user will only receive new data if the reload the entire page. So a 'hard refresh' but might be useful..
Another thing you can do is asking yourself why you don't want to call that endpoint multiple times? Is the data set to large to receive? In that case use pagination and serve it in pieces.

ReactJs - Reuseable Redux Service

I understand the concept of Redux's actions, reducers, and mapping to stores.
I have been able to successfully execute Redux into my app.
I was going along merrily using React's contextTypes for child components that needed data from Redux that had been called before.
Then I ran into a strange situation where the data was mutated by a child. When I posted the problem on SO, a member told me I should be using contextTypes sparingly anyway.
So the only way to overcome my problem was map to stores, AGAIN, in the child's parent, like a higher component of the parent had done earlier, and pass that data to the child as props.
But that seems all wrong to me. Mapping to the same store again? Why? What am I not understanding? Why do I have to write this on every component that needs the same data another component mapped to?
export default class Foo extends Component {
.....
// I DID THIS STUFF IN A HIGHER COMPONENT.
// WHY MUST I REPEAT MYSELF AGAIN?
// WHAT AM I NOT UNDERSTANDING?
static propTypes = {
children: PropTypes.node.isRequired,
dispatch: PropTypes.func.isRequired,
products: PropTypes.array
};
componentDidMount() {
const { dispatch } = this.props;
dispatch(fetchProductsIfNeeded());
}
.....
}
const mapStateToProps = (state) => {
const {productsReducer } = state;
if (!productsReducer) {
return {
isFetching: false,
didInvalidate: false,
error: null,
products: []
};
}
return {
error: productsReducer.error,
isFetching: productsReducer.isFetching,
didInvalidate: productsReducer.didInvalidate,
products: productsReducer.products
};
};
export default connect(mapStateToProps)(Foo);
I looked at containers, but it appears to me that containers wrap all dumb components in them at once as such ...
<ProductsContainer>
<ProductsComponent />
<ProductSpecialsComponent />
<ProductsDiscountedComponent />
</ProductsContainer>
And that is not what I want. I thought, like a service I could use that container in each respective dumb component as a such ....
<ProductsContainer>
<ProductsDiscountedComponent />
</ProductsContainer>
<ProductsContainer>
<ProductSpecialsComponent />
</ProductsContainer>
<ProductsContainer>
<ProductsComponent />
</ProductsContainer>
Right now in order to get my 3 sub components illustrated above, each one of them has to map to stores and that just seems all wrong.
I cannot find anything that I can grasp as a solution.
Question:
Is there a way I can map to a particular store just once, and call on that "service" for those components that need that data?
If so, examples would be appreciated.
Post Script:
I though perhaps if I could perform the 'mapping service' as a pure JavaScript function o/s of react, and just import that function in the components that need it, that would solve the problem, but I have not seen any examples of Redux stores being mapped o/s React.
UPDATE:
I posted the solution here ......
React-Redux - Reuseable Container/Connector
First, an aside about your past problems. It's true that context is not appropriate for something like this. You should also be worried about the mutation you mentioned. If you're using a Redux store, the data that exits it should always be immutable. Perhaps a library like Immutable.js would help there.
Now let's turn to the matter at hand. Perhaps what you aren't fully grokking is what a "dumb" component is. A dumb component should be stateless and a pure:
const Product = ({ name, comments }) => (
<div>
<h1>{name}</h1>
<CommentsList comments={comments} />
</div>
);
The component gets everything it needs from props. Now there are a number of ways to get data into this component, but they are all based on props. For example, the following is the most straightforward:
const ProductList = ({ products }) => (
<div>
{products.map( p => <Product product={product} /> )}
</div>
);
class App extends Component {
getInitialState () {
return { products: [] };
}
componentDidMount () {
// connect to store, blah blah...
}
render () {
return (
<div>
{/* blah blah */}
<ProductsList products={this.state.products} />
{/* blah blah */}
</div>
);
}
}
As you can see from the example, the entire components tree will get its state from props that are simple passed down from one connection to the store. Aside from App, all components are dumb, stateless, and predictable.
But there are also cases where connecting the entire tree through props is impractical and where we need localized connections to our stores. That's where HOCs can be hugely helpful:
const WithProducts = Comp => class WrappedComponent extends Component {
getInitialState () {
return { products: [] };
}
componentDidMount () {
// connect to store, blah blah...
}
render () {
return (
<Comp products={this.state.products} {...this.props} />
);
}
}
const ProductListWithProducts = WithProducts( ProductList );
Now any component we so wrap will receive the list of products from the store as a prop - no code duplication required. No repeating yourself. Notice how I did not alter the ProductList or Product components to make this work: those components are too dumb to care.
The majority of the components in any React app you create should be so dumb.
As another aside, you should not be worried about calling your store more than once. If you are worried about that, there's something wrong with the store implementation because calls to stores should be idempotent. You can use actions and so forth to populate the stores, but that should be wholly independent from getting values from stores. There should be no performance or network penalty form well-design store retrievals (and, again, using libraries like Immutable can help here too).

Categories

Resources