I'm using React Context to communicate between components (actions)
Context.Consumer resides inside the return() part of a component and as such doesn't provide props to the component (unless Consumer is put in parent component but then we're back to the badness of props drilling)
Apollo client is applied by Apollo's compose() to that the result is available in component props but more importantly, a change in props leads to Apollo refetch
Using Context, this automatic refetch pathway is broken and I see no other way but to bring the whole GraphQl document into the component (after Context.Consume) to manually do a refetch when Context brings in action.
This is a radical change in pattern and I really wonder if I'm missing some big picture?
const Child = ({ gqlGetData }) => {
if (gqlGetData.loading) return "..."
const { componentData } = gqlGetData
return (
<AppControl.Consumer>
{appControl => (
<React.Fragment>
---component rendering logic here ---
---but needs also refetch logic and GraphQl document---
)}
</React.Fragment>
)}
</AppControl.Consumer>
)
}
Related
in React Typescript i have a parent component with two independant child components:
export const Editor = () => {
return (
<>
<TableTotal />
<hr />
<TableDetail />
</>
);
};
And after some actions in TableDetail i have to completely reload data in TableTotal component.
In TableTotal i have code like this:
useEffect(() => {
getData();
}, []);
where getData is a function with fetch from the server.
How can i told the TableTotal component to reload data from the TableDetail Component (calling the getData() function) ?
I use React with Typescript.
Thanks
The concept you need to solve this is called lifting state up in the React documentation. You store the data shared by TableTotal and TableDetail inside Editor, and then pass the data to each child component as props. That way, if one component causes the data to change, the other one will re-render with the new data, too.
There are 2 solutions out there. Either use the prop drilling method or take your app states to global (context/redux).
Prop Drilling: Initialize state in the Editor Component, if you change something in any component, make that state dependency of the useEffect and it will call getData() and re-render the components.
I'm having a lot of trouble learning to properly load data into state in my todo app.
I have a next.js page component pages/index.tsx where I load data from my API via getServerSideProps and return it as a page prop called tasksData.
The tasksData is being returned properly and I can access them in my page component just fine via prop destructuring: const Home = ({ tasksData }: Home) => { }
I also have a React Context provider in _app.tsx called BoardProvider. This stores state for my task board, and employs useReducer() from the React Context API to update this state in context consumers, such as pages/index.tsx.
The challenge I am facing is how to make my UI's "source of truth" the state stored in my context provider (eg. const { { tasks }, dispatch } = useBoard();, rather than the page page props returned from my API (eg. the tasksData prop).
One approach I considered was to simply load the data in getServerSideProps and then set the state via a dispatched action in a useEffect hook:
useEffect(() => {
// On first render only, set the Context Provider state with data from my page props.
dispatch({ type: TaskAction.SET_TASKS, payload: tasksData });
});
However, this doesn't seem to be working because sometimes tasksData is undefined, presumably because Next.js has not yet made it available on page mount.
Another suggestion I heard was to fetch the data and pass it as pageProps to my Context Provider in _app.tsx. I believe this means using getInitialProps() in _app.tsx so that my provider's initial state is populated by my API. However, this disabled static optimization and other useful features.
Can anyone help me out with some pseudocode, documentation, or examples of how to use getServerSideProps in combination with React Context API?
Couple of points:
getServerSideProps should be invoked before the page is even rendered. So theoretically your tasksData is undefined is a bug! You can't have a server data to be unavailable unless you really really intend to have that happen in the first place.
Assuming getServerSideProps is always returning the right data, but you want to use your own data to override it. In your context, you can have this logic.
const Home = ({ tasksData }) => {
const value = { tasksData: {
// let me override it
}}
return (
<Context.Provider value={value}>
...
<Context.Provider>
)
}
If you have the context provided under a page, the above code is all you need. But if your context is provided in a root (parent of a page), you can still add the above code to re-provide the same context again with overridden value. Because this is how a context is designed, read https://javascript.plainenglish.io/react-context-is-a-global-variable-b4b049812028 for more info.
I'm trying to create some components that need to communicate their loading states to the parent component.
Here's an example:
const Parent = () => {
// Loading
const [hasFirstFinishedLoading, setHasFirstFinishedLoading] = React.useState(false);
const [hasSecondFinishedLoading, setHasSecondFinishedLoading] = React.useState(false);
const children = (
<>
<First
onFinishedLoading={() => setHasFirstFinishedLoading(true)}
/>
<Second
onFinishedLoading={() => setHasSecondFinishedLoading(true)}
/>
</>
);
if (hasFirstFinishedLoading && hasSecondFinishedLoading) {
return <>{children}</>
}
return <LoadingComponent />
}
Essentially, the idea is that I render a Loading component while the children components are loading (i.e making some network requests). However, this doesn't work because the children component are not mounted, and so they don't start their loading process.
Is there a way to mount the children components so they can load?
Some approaches I've already considered:
Create a top level context that handles loading (i.e network requests) and let that control rendering. I can just render the children components once the context has finished its network requests.
Render the children components, but set display:none.
Are there any other approaches?
Your approaches sound reasonable. As soon as React 18 is released, however, Suspense will be officially available. It is built for exactly this use case, but currently only experimental support is available. Check out the react docs for more info.
Context provides a way to pass data through the component tree without having to pass props down manually at every level. This is great!
but I'm wondering how to use it with getDerivedFromProps()
For example, if I have a prop sent via Context in top level of the app, that said it's window.location.href, and I need to take action in the child component based on the href, e.g. fetch the data.
Using getDerivedStateFromProps(), I have to write something like the following:
getDerivedStateFromProps(nextProps, state) {
var stateRev = null
var pathname = hrefToPath(nextProps.href)
if (pathname != state.pathname) {
stateRev = {}
Object.assign(stateRev, {
pathname,
book: source.find()
})
}
return stateRev
}
However, if I write the code like the above, I have to send the window.location.href through the levels. What I need to know is if the prop in the context changed, I need to update the state.
I see no way to know the prop in the context changed or not. Is there anything I need to know about the context api and getDerivedStateFromProps?
Thank you.
If you want to consume context in lifecycle methods you can use contextType. The problem with this approach is that getDerivedStateFromProps is static and cannot access instance variables.
So solution I see is to wrap your component in High Order Component, like this
const WithContext = (Component) => {
return (props) => (
<CustomContext.Consumer>
{value => <Component {...props} value={value} />}
</CustomContext.Consumer>
)
}
In this case you'll get context as part of props
getDerivedFromProps is not for that
DOCS - tips for getDerivedFromProps: 'If you need to perform a side effect (for example, data fetching or an animation) in response to a change in props, use componentDidUpdate lifecycle instead.'
Also 'This method doesn’t have access to the component instance.' - then no this.context available.
If you need to react on context prop change - use Context.Consumer. Use componentDidUpdate to compare props (consumer provides context value as prop) and conditionally fetch data.
I'm still new to react/redux, after getting something like this to function
User.js
class User extends React.Component {
componentWillMount() {
this.props.fetchUser(.....);
}
render() {
return (
<Profile />
)
}
export default connect(null, {fetchUser})(User);
Profile.js
class Profile extends React.Component {
render() {
const { user } = this.props
return (
<h1>{user.profile.name}</h1>
)
}
const mapStateToProps = state => ({
user: state.store.user
});
export default connect(mapStateToProps, {})(Profile)
actions.js
export const fetchUser = (.....) => dispatch => {
fetch()
.....
}
reducers.js
case FETCH_USER:
return {
...state,
user: action.payload.user
};
As I understand it, the User component calls an action (fetchUser) from connect on componentWillMount(). That action calls an api, gets the data and the reducer adds that to the store within the state. The Profile component can then use connect to map the data from fetchUser in the store and display that data.
After reading some tutorials including https://github.com/reactjs/redux/blob/master/docs/basics/UsageWithReact.md
It looks like things can be simplified a bit without using classes.
If I were to change the User and Profile components to a more functional way, how would I do it?
eg.
const User = () => {
return (
<Profile />
)
}
how do I dispatch the fetchUser action and how do I simulate it to be called with the flow of componentWillMount()?
or am I just over complicating things?
There is also a way to support lifecycle methods in functional components.
https://www.npmjs.com/package/react-pure-lifecycle
import React from 'react';
import lifecycle from 'react-pure-lifecycle';
// create your lifecycle methods
const componentDidMount = (props) => {
console.log('I mounted! Here are my props: ', props);
};
// make them properties on a standard object
const methods = {
componentDidMount
};
const FunctionalComponent = ({children}) => {
return (
<div>
{children}
</div>
);
};
// decorate the component
export default lifecycle(methods)(FunctionalComponent);
I think you should keep using statefull components with redux...
https://medium.com/#antonkorzunov/2-things-about-purecomponent-you-probable-should-know-b04844a90d4
Redux connect — is a PureComponent.
Yes — a very important thing, a HoC for a molecule is a pure one. And works even inside other pure components. And gets store from a current context.
Same is working, for example, for styled-component — you can wrap it with PureComponent, but it will still react to Theme changes.
Solution is simple — bypass logic, use old school events bus, subcribe, wait and emit events.
Styled-componets:
componentWillMount() {
// subscribe to the event emitter. This
// is necessary due to pure components blocking
// context updates, this circumvents
// that by updating when an event is emitted.
const subscribe = this.context[CHANNEL];
this.unsubscribe = subscribe(nextTheme => { <----- MAGIC
React-redux:
trySubscribe() {
if (shouldSubscribe && !this.unsubscribe) {
this.unsubscribe =
this.store.subscribe(this.handleChange); <----- MAGIC
}
}
componentDidMount() {
this.trySubscribe();
}
Thus, even if parent Pure Component will block any update enables you to catch a change, store update, context variable change, or everything else.
So — something inside pure components is very soiled and absolutely impure. It is driven by side effects!
But this bypass straight logic flow, and works just differently from the rest of application.
So — just be careful. And don’t forget about magic.
Aaaand….
And this is a reason, why any redux store update will cause redraw in each connected component, and why you should use reselect just next to connect HoC —
to stop unnecessary change propagation.
But you should read this from another point of view:
redux-connect is a source of a change propagation.
redux connect is the end of a change propagation. It is still PureComponent.
And this leads to quite handy thing — you can control change propagation with redux-connect only. Just create a boundaries for a change. Lets talk about this in another article.
Conclusion
Pure components keep your application fast. Sometimes — more predictable, but often — less predictable, as long they change the way application works.
Stateless components are not pure, and may run slower than PureComponents by any kind.
But… if you very wish to create a fast application with good user experience — you have to use Pure Component.
No choice. But, now — you know hidden truth, and knew some magic…
React recommends that ajax request be made in componentDidMount(), rather than in componentWillMount(). For more info on this, read this post.
Since you want to make ajax requests in componentDidMount(), you need a class. There are two ways of writing component definitions: functional component and the class component. Functional components are more concise, but you don't get component lifecycle methods like componentDidMount(). Think of it as just a render function that takes props as inputs and outputs DOMs (in JSX). To override those lifecycle methods, you need to define them as a class.
If you want to use Redux, and want to make ajax requests in a Redux action, you should import the action creator function (fetchUser(..) in your case) that makes the ajax request, and dispatch(fetchUser(..)) in componentDidMount(). connect(..)ed components get dispatch(..) function passed to it by Redux store.
If you want to see how it's done in other redux apps, see the official example apps in the redux.js repo, paying attention to actions and containers: https://github.com/reactjs/redux/tree/master/examples
In Your case you can continue with statefull components no wrong in that
,If you need to go with functional way
There is a work arround
https://github.com/mobxjs/mobx/issues/162
Suggestion
Calling the api in componentDidMount will make sense than
componentWillMount , Because you can show the user something is
fetching.
I think,User component is designed nicely.It will act as a container for Profile to provide the Data.
Instead of making Profile component class oriented,it should be Stateless.
Lets User component pass the required data for Profile component.
You don't need to connect Profile component using redux-connect.Just render it as a Child component of User.
Profile
const Profile = (props) => {
const {user, likeProfile} = props;
//likeProfile()//call like this using dom event or programmatically.
return (
<h1>{user.profile.name}</h1>
)
}
You need to make some changes in User component.
Get the state for Profile component via mapStateToProps.
class User extends React.Component {
componentWillMount() {
this.props.fetchUser(.....);
}
render() {
const {user, likeProfile} = this.props;
return (
<Profile user= {user} likeProfile={likeProfile} /> //passed the user data to Profile component vua User
)
}
Map the user state for Profile in User connect.
const mapStateToProps = (state)=>{
return{
user : state.somereducerkey.user //this will be accessible in Profile via props { user}
}
}
export default connect(mapStateToProps, {fetchUser, likeProfile})(User);