ReactJs - Reuseable Redux Service - javascript

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

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.

How and where to pass JSON data as a prop when creating components

I am getting some Club information from a JSON I want to use in my React component 'Club'. I created a component ClubList in which all Club components with their corresponding name should be created but I don't know where I should make the HTTP request and where to save it, so I can use it in the return statement.
I tried saving all titles in an array but I stopped at the point I had to pass the titles to each Club element. I just started working with ReactJS so I am a basically complete beginner in ReactJS (Not in JS though).
This is the ClubList class
class ClubList extends React.Component {
render() {
return (
<div className="Clublist">
<Club title="Club1" />
<Club title="Club2" />
...
...
...
</div>
)
}
}
And that is the Club class
class Club extends React.Component {
clubProp = {...}
render() {
return (
<div className="Club">
<div className="image-container">
<img src={this.clubProp.imageSrc} width="300px" height="300px"/>
</div>
<h2>{this.clubProp.name}</h2>
<div className="Business-information">
<div className="Business-address">
<p>{this.clubProp.address}</p>
<p>{this.clubProp.city}</p>
<p>{this.clubProp.zipCode}</p>
</div>
<div className="Business-reviews">
<h3>{this.clubProp.category}</h3>
<h3 className="rating">{this.clubProp.rating}</h3>
<p>90 reviews</p>
</div>
</div>
</div>
)
}
}
I will use an API to get the Club-names but I don't know how I can organize the variables to be accessible in the right places since I don't quite grasp how the scopes in React work. I already have the code for getting the JSON ready, just need to know where to put it and how to pass the values in
Your basic syntax would look like the following. Your component will maintain clubs using component state. In the componentDidMount lifecycle function, you can make your api call and then store the results in your component's state. Any time you call setState, your component will re-render.
class ClubList extends React.Component {
state = {
clubs: []
};
componentDidMount = async () => {
const clubs = await this.fetchClubs();
this.setState({ clubs });
}
render() {
const { clubs } = this.state;
return (
<div className="Clublist">
{clubs.map(club => (
<Club title={club.title} />
)}
</div>
);
}
}
Eventually you can pull out state management from all of your components and use something like redux or MobX and let your components focus solely on rendering html.
Where you should make the API request?
Ideally, we use redux-sagas or redux-thunk as middleware while making API requests. However, since, you're just getting started, you could make the API call in the componentDidMount lifecycle method of your ClubList component.
Now, I am assuming that you receive an array of Clubs. You could map over this array and render each Club component.
Where you should store this data?
Common practice is to use a state-management library like redux with react. It helps scale and maintain your app better. However, you could also use the state of the ClubList component to store the data of your API call.
I hope this was helpful.
From the docs, you make API calls in the componentDidMount life-cycle method. I'd recommend looking at the docs for examples:
https://reactjs.org/docs/faq-ajax.html
The docs use the browser's fetch method to make the request, but I'd personally recommend using axios. https://www.npmjs.com/package/axios, since it's a bit more straight forward.
React uses state, which can be passed down through the component tree. As you are using class components, a typical way of getting your JSON data into the app's state would be to fetch the data in the componentDidMount() lifecyle method of your top level component, and run this.setState({clubProp: result.data}) in the fetch/axios callback. You can pass it to children where they are available as props.
I would argue that Redux is overkill - and that it would be better to defer learning it until you have a state management problem. The new hooks implementation and context API will also change best practices for state management. The guy who created Redux says "Flux libraries are like glasses: you’ll know when you need them."
// Here is the simplified example:
class ClubList extends React.Component {
constructor(props) {
super(props);
this.state = {
ClubDetails: [],
};
}
componentDidMount() {
fetch("api link put here in quotes")
.then(results => {
return results.json();
}).then(data => {
let ClubDetails = data;
this.setState({ ClubDetails: ClubDetails });
})
}
render() {
const ClubDetails = this.state.ClubDetails;
const listItems = ClubDetails.map((clubProp,index) =>
<Club key={index} clubProp={clubProp}/>
);
return (
{listItems}
);
}
}
class Club extends React.Component {
render() {
return (
<div className="Club">
<div className="image-container">
<img src={this.clubProp.imageSrc} width="300px" height="300px"/>
</div>........
My recommendation is using fetch middleware in redux to control all datas in props(You can find in redux github examples), besides the responsive data, you can also monitor fetching status across components.

ReactJS - Lifting state up vs keeping a local state

At my company we're migrating the front-end of a web application to ReactJS.
We are working with create-react-app (updated to v16), without Redux.
Now I'm stuck on a page which structure can be simplified by the following image:
The data displayed by the three components (SearchableList, SelectableList and Map) is retrieved with the same backend request in the componentDidMount() method of MainContainer. The result of this request is then stored in the state of MainContainer and has a structure more or less like this:
state.allData = {
left: {
data: [ ... ]
},
right: {
data: [ ... ],
pins: [ ... ]
}
}
LeftContainer receives as prop state.allData.left from MainContainer and passes props.left.data to SearchableList, once again as prop.
RightContainer receives as prop state.allData.right from MainContainer and passes props.right.data to SelectableList and props.right.pins to Map.
SelectableList displays a checkbox to allow actions on its items. Whenever an action occur on an item of SelectableList component it may have side effects on Map pins.
I've decided to store in the state of RightContainer a list that keeps all the ids of items displayed by SelectableList; this list is passed as props to both SelectableList and Map. Then I pass to SelectableList a callback, that whenever a selection is made updates the list of ids inside RightContainer; new props arrive in both SelectableList and Map, and so render() is called in both components.
It works fine and helps to keep everything that may happen to SelectableList and Map inside RightContainer, but I'm asking if this is correct for the lifting-state-up and single-source-of-truth concepts.
As feasible alternative I thought of adding a _selected property to each item in state.right.data in MainContainer and pass the select callback three levels down to SelectableList, handling all the possible actions in MainContainer. But as soon as a selection event occurs this will eventually force the loading of LeftContainer and RightContainer, introducing the need of implementing logics like shouldComponentUpdate() to avoid useless render() especially in LeftContainer.
Which is / could be the best solution to optimise this page from an architectural and performance point of view?
Below you have an extract of my components to help you understand the situation.
MainContainer.js
class MainContainer extends React.Component {
constructor(props) {
super(props);
this.state = {
allData: {}
};
}
componentDidMount() {
fetch( ... )
.then((res) => {
this.setState({
allData: res
});
});
}
render() {
return (
<div className="main-container">
<LeftContainer left={state.allData.left} />
<RightContainer right={state.allData.right} />
</div>
);
}
}
export default MainContainer;
RightContainer.js
class RightContainer extends React.Component {
constructor(props) {
super(props);
this.state = {
selectedItems: [ ... ]
};
}
onDataSelection(e) {
const itemId = e.target.id;
// ... handle itemId and selectedItems ...
}
render() {
return (
<div className="main-container">
<SelectableList
data={props.right.data}
onDataSelection={e => this.onDataSelection(e)}
selectedItems={this.state.selectedItems}
/>
<Map
pins={props.right.pins}
selectedItems={this.state.selectedItems}
/>
</div>
);
}
}
export default RightContainer;
Thanks in advance!
As React docs state
Often, several components need to reflect the same changing data. We
recommend lifting the shared state up to their closest common
ancestor.
There should be a single “source of truth” for any data that changes
in a React application. Usually, the state is first added to the
component that needs it for rendering. Then, if other components also
need it, you can lift it up to their closest common ancestor. Instead
of trying to sync the state between different components, you should
rely on the top-down data flow.
Lifting state involves writing more “boilerplate” code than two-way
binding approaches, but as a benefit, it takes less work to find and
isolate bugs. Since any state “lives” in some component and that
component alone can change it, the surface area for bugs is greatly
reduced. Additionally, you can implement any custom logic to reject or
transform user input.
So essentially you need to lift those state up the tree that are being used up the Siblings component as well. So you first implementation where you store the selectedItems as a state in the RightContainer is completely justified and a good approach, since the parent doesn't need to know about and this data is being shared by the two child components of RightContainer and those two now have a single source of truth.
As per your question:
As feasible alternative I thought of adding a _selected property to
each item in state.right.data in MainContainer and pass the select
callback three levels down to SelectableList, handling all the
possible actions in MainContainer
I wouldn't agree that this is a better approach than the first one, since you MainContainer doesn't need to know the selectedItems or handler any of the updates. MainContainer isn't doing anything about those states and is just passing it down.
Consider to optimise on performance, you yourself talk about implementing a shouldComponentUpdate, but you can avoid that by creating your components by extending React.PureComponent which essentially implements the shouldComponentUpdate with a shallow comparison of state and props.
According to the docs:
If your React component’s render() function renders the same result
given the same props and state, you can use React.PureComponent for a
performance boost in some cases.
However if multiple deeply nested components are making use of the same data, it makes sense to make use of redux and store that data in the redux-state. In this way it is globally accessible to the entire App and can be shared between components that are not directly related.
For example consider the following case
const App = () => {
<Router>
<Route path="/" component={Home}/>
<Route path="/mypage" component={MyComp}/>
</Router>
}
Now here if both Home and MyComp want to access the same data. You could pass the data as props from App by calling them through render prop. However it would easily be done by connecting both of these components to Redux state using a connect function like
const mapStateToProps = (state) => {
return {
data: state.data
}
}
export connect(mapStateToProps)(Home);
and similarly for MyComp. Also its easy to configure actions for updating relevant informations
Also its particularly easy to configure Redux for your application and you would be able to store data related to the same things in the individual reducers. In this way you would be able to modularise your application data as well
My honest advice on this. From experience is:
Redux is simple. It's easy to understand and scale BUT you should use Redux for some specific use cases.
Since Redux encapsulates your App you can think of storing stuff like:
current app locale
current authenticated user
current token from somewhere
Stuff that you would need on a global scale. react-redux even allows for a #connect decorator on components. So like:
#connect(state => ({
locale: state.locale,
currentUser: state.currentUser
}))
class App extends React.Component
Those are all passed down as props and connect can be used anywhere on the App. Although I recommend just passing down the global props with the spread operator
<Navbar {...this.props} />
All other components (or "pages") inside your app can do their own encapsulated state. For example the Users page can do it's own thing.
class Users extends React.Component {
constructor(props) {
super(props);
this.state = {
loadingUsers: false,
users: [],
};
}
......
You would access locale and currentUser through props because they were passed down from the Container components.
This approach I've done it multiple times and it works.
But, since you wanted to really consolidate the knowledge of React first, before doing Redux you can just store your state on the top-level component and pass it down to the children.
Downsides:
You're gonna have to keep passing them down into inner level components
To update state from the inner level components you're gonna have to pass the function that updates the state.
These downsides are a little boring and cumbersome to manage. That's why Redux was built.
Hope I helped. good luck
By using Redux you can avoid such callbacks and maintain the whole state in one single store - so make your parent component connected component - and make left and right components dumb ones - and just pass in the props you get from parent to child - and you don't have to worry about callbacks in this case.

ReactJS - component to change what is rendered by another

I am creating a reactJS app that will show a list of mobile phones. I am currently calling an API, setting state to the response, and then displaying the contents. I am now looking to add sort order to this but am having difficulty.
When the user goes to /phones/Apple/iPhone, the routing will render the following component that calls the api, sets the state and passes the data to the results component...
export default class Phones extends Component {
constructor(props) {
super(props);
this.state = {
data: null,
make: this.props.params.make || null,
model: this.props.params.model || null
};
}
componentDidMount(){
const makeQuery = this.state.make ? this.state.make + '/' : '';
const modelQuery = this.state.model ? this.state.model : '';
const fetchUrl = '/api/phones/' + makeQuery + modelQuery;
fetch(fetchUrl, {
headers: {
Accept: 'application/json'
}
}).then(response => {
if (response.ok) {
response.json().then(json=> {
this.setState({data: json});
});
}
});
}
render() {
if (this.state.data) {
const currentUrl = this.props.location.pathname;
return (
<section className="phones">
<Filters
data={this.state.data}
currentUrl={currentUrl} />
<Results
data={this.state.data} />
</section>
)
}
}
}
The Results Component will map through the data and render the list of phones.
In the Filters Component I then have a dropdown that allows user to sort the results by price value, and this sets the state too.
export default class Filters extends Component {
constructor(props){
super(props);
this.state = {
value: 0
}
this.onChange = this.onChange.bind(this);
}
onChange(e){
this.setState({value: e})
}
render() {
return (
<div>
<p>Price</p>
<select onChange={this.onChange}>
<option value='asc'>low to high</option>
<option value='desc'>high to low</option>
</select>
</div>
);
}
}
The problem I'm having is that I do not know how I can apply this filter to the component which renders the results, and what is the best way to go about this?
I have started reading in redux, but am confused if the phones data and filters should be in a store as it is temporary and will change if they go to another page i.e /phones/Samsung/Galaxy/
Any help is appreciated
Typically in React, a parent container will handle state for all sub-components - data and props flow in one direction, down through the component tree.
Redux offers a way to give containers access to a method for updating state.
This page from the Redux docs provide instructions for how to integrate with React.
Redux provides a single object that contains the entire application state. Then an additional NPM module, react-redux, provides two functions for "connecting" a component to global Redux state: mapStateToProps and mapDispatchToProps.
Using this approach, you can have your Filters container set a toggle in global state:
state = {
phones: [],
sortBy: 'price',
sortOrder: 'asc',
}
Or similar. You can then use mapStateToProps to gain access to the sort state slices, and mapDispatchToProps to "dispatch" actions that update the state.
The Redux docs are excellent, and written with simplicity and beginners in mind.
So there are a couple of ways to do this. You mentioned you're just beginning to look into Redux, and I'd encourage you to keep on that path. It will help you much in the world of application state management. That said, if you are doing this with just React:
The parent component is Phones, so write a helper method in Phones to keep track of the filter (make sure you set a default filter state for when the component first constructs or mounts like you did with make and model):
setFilter(filter) {
this.setState({filter});
}
Pass the filter from the Phones component state to both the Filters and Results components. Also pass the setFilter class method into the child Filters component (All this is done in your Phones component's render method):
return (
<section className="phones">
<Filters
filter={this.state.filter}
onChange={this.setFilter}
data={this.state.data}
currentUrl={currentUrl} />
<Results
data={this.state.data}
filter={this.state.filter}/>
</section>
)
Change your Filter component so that its onChange event is the setFilter handler you passed into it, and the value prop in the <select> component is the filter we passed in (2).
I'll leave 3 to you. I think you can figure it out :). Note that you now have access to the filter value as a prop in your results class and you can sort in that component.
I have started reading in redux, but am confused if the phones data and filters should be in a store as it is temporary and will change if they go to another page i.e /phones/Samsung/Galaxy/
Redux is a great solution to this problem. By externalising the state, both components can easily access the necessary values.
Your concern about the data changing should not be so, as this is an important and common situation for redux stores. Even the todo list example from their documentation implements a filter component similarly to what you are describing.
Another benefit is that if you use the redux-thunk middleware (or whichever async middleware you want), you can also move your fetch call out of the component so it becomes a little bit simpler.
There are plenty of examples floating around for this kind of setup so I won't bother writing any out for you now, but I'll update this answer for any specific requests in the comments.
Alternatively, you can use callbacks to pass data up and down between components.
This image from this blog has is a great illustration of how this can be achieved

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