I've created a react app driven by Apollo client and graphQL.
My schema is defined so the expected result is an array of objects ([{name:"metric 1", type:"type A"},{name:"metric 2", type:"type B"}])
On my jsx file I have the following query defined:
query metrics($id: String!) {
metrics(id: $id) {
type
name
}
}`;
I've wrapped the component with Apollo HOC like so:
export default graphql(metricsQuery, {
options: (ownProps) => {
return {
variables: {id: ownProps.id}
}
}
})(MetricsComp);
The Apollo client works fine and returns the expected list on the props in the render method.
I want to let the user manipulate the results on the client (edit / remove a metric from the list, no mutation to the actual data on the server is needed). However since the results are on the component props, I have to move them to the state in order to be able to mutate. How can I move the results to the state without causing an infinite loop?
If apollo works anything like relay in this matter, you could try using componentWillReceiveProps:
class ... extends Component {
componentWillReceiveProps({ metrics }) {
if(metrics) {
this.setState({
metrics,
})
}
}
}
something like this.
componentWillReceiveProps will be deprecated soon (reference link)
If you are using React 16 then you can do this:
class DemoClass extends Component {
state = {
demoState: null // This is the state value which is dependent on props
}
render() {
...
}
}
DemoClass.propTypes = {
demoProp: PropTypes.any.isRequired, // This prop will be set as state of the component (demoState)
}
DemoClass.getDerivedStateFromProps = (props, state) => {
if (state.demoState === null && props.demoProp) {
return {
demoState: props.demoProp,
}
}
return null;
}
You can learn more about this by reading these: link1, link2
you can use this:
import {useState} from 'react';
import {useQuery} from '#apollo/client';
const [metrics,setMetrics]=useState();
useQuery(metricsQuery,{
variables:{id: ownProps.id},
onCompleted({metrics}){
setMetrics(metrics);
}
});
Related
In React all props are updated and propagated to children automatically which is nice but it slows down and requires lots of optimization at some point.
So I'm building an app with SolidJS using Context + createStore patterng and I'm having problems with consuming that state.
I'd like to create AppProvider component that manages State props and Dispatch functions. The Provider will be performing all operations on appStore, implement functions and serve them all via AppContextState and AppContextDispatch providers.
Then I need to consume that data to update components that are dependent on it reactively.
Look at the code below:
/// index.tsx
import { render } from 'solid-js/web';
import { AppProvider } from '#/providers/AppProvider';
import App from './App';
render(() => (
<AppProvider>
<App />
</AppProvider>
), document.getElementById('root') as HTMLElement);
/// AppProvider.tsx
import { createContext, useContext, JSX } from 'solid-js';
import { createStore } from 'solid-js/store';
// Interfaces
interface IAppState {
isConnected: boolean;
user: { name: string; }
}
interface IAppDispatch {
connect: () => Promise<void>;
disconnect: () => Promise<void>;
}
// Initialize
const initialState = {
isConnected: false,
user: { name: '' }
}
const initialDispatch = {
connect: () => {},
disconnect: () => {}
}
// Contexts
const AppContextState = createContext<IAppState>();
const AppContextDispatch = createContext<IAppDispatch>();
export const useAppState = () => useContext(AppContextState);
export const useAppDispatch = () => useContext(AppContextDispatch);
// Provider
export const AppProvider = (props: { children: JSX.Element }) => {
const [appStore, setAppStore] = createStore<IAppState>(initialState);
async function connect() {
setAppStore("isConnected", true);
setAppStore("user", "name", 'Chad');
}
async function disconnect() {
setAppStore("isConnected", false);
setAppStore("user", "name", '');
}
return (
<AppContextState.Provider value={appStore}>
<AppContextDispatch.Provider value={{ connect, disconnect }}>
{props.children}
</AppContextDispatch.Provider>
</AppContextState.Provider>
)
}
/// App.tsx
import { useAppState, useAppDispatch } from '#/providers/AppProvider';
export default function App() {
const { user, isConnected } = useAppState();
const { connect, disconnect } = useAppDispatch();
return (
<Show when={isConnected} fallback={<button onClick={connect}>Connect</button>}>
<button onClick={disconnect}>Disconnect</button>
<h3>Your Name: {user.name}</h3>
</Show>
)
}
This component will show a button that should run the connect function and update isConnected state and make the component within <Show> block visible but it doesn't do anything.
I verified that state is being updated by logging data of appStore in connect method.
When I change the component to depend on user.name instead isConnected it works
<Show when={user.name} fallback={<button onClick={connect}>Connect</button>}>
<button onClick={disconnect}>Disconnect</button>
<h3>Your Name: {user.name}</h3>
</Show>
However my app has many components depending on various data types, including boolean that for some doesn't work in this example with SolidJS.
I'd like to know what am I doing wrong here and understand what is the best way to share state between components. I keep reading documentation and fiddling with it but this particular problem bothers me for a past few days.
Plain Values in Solid cannot be tracked
The problem here is that primitive values / variables cannot be reactive in solid. We have two ways of tracking value access: Through function calls, and through property getters/proxies (which use signals under the hood).
So, what happens when you access a store property?
const state = useAppState();
createEffect(() => {
console.log(state.isConnected)
})
In this case, the property access is occurring within the effect, so it gets tracked, and reruns when the property value updates. On the other hand, with this:
const { isConnected } = useAppState();
We are accessing the property at the top level of the component (which is untracked and not reactive in solid). So even though we use this value in a context that is reactive (like the when prop in `), we can't run any special under-the-hood tracking to set up updates.
So why did user.name work?
The reason is that stores are deeply reactive (for primitives, objects and arrays), so
const { user } = useAppState();
Means that you are eagerly accessing the user object (so if the user property changes, you won't get updated), but the properties of the user object were not accessed yet, they only get accessed further on, in <Show when={user.name}>, so the property access user.name is able to be tracked.
I am trying to useStatic Query and GraphQL to get a simple title from
Contentful, pass it to state and then show in the render. I cant make it work. I am attaching an image showing my current setup and errors.
Possible problems: 1. the query returns an array, and I need to change it into a string, or access 0 element, the first one, because my content type is just one, as it is a onepage.
Placing of the query in the component, I am not sure if it can be in the constructor of an component
For comparison: in the screen from my file you can see a variable name showing Josh Perez, when I uncomment it and add it to this.state = { dataTest: name}, then in RENDER: this.state.dataTest returns the name Josh Perez well, so passing a variable to state works, but passing a string from graphql query is not possible for me...
I have a limitation which is that I need to create my page component with a class, because of the fact that in the Component did mount I am placing some JQuery, which works well for me.
THIS IS MY TEST CODE
1. In Constructor
class IndexPage extends React.Component {
constructor(props) {
super(props);
// this.state = { data: null };
const name = 'Josh Perez';
this.state = { dataTest: name };
}
In render
{this.state.dataTest}
This works, the variable name is passed to state and shown in render.
However, I want to show in this way a simple text string from Contentful. So I am trying code like this (error message is shown in the screens):
class IndexPage extends React.Component {
constructor(props) {
super(props);
// this.state = { data: null };
//const name = 'Josh Perez';
const data = useStaticQuery(graphql`
query {
allContentfulHomepage (limit: 1) {
edges {
node {
section1Title
}
}
}
}
`)
this.state = { dataTest: data };
It turns out, that the below suggested solution works. I am putting below
my attempt at callingfurther content. It does not work. It displays the following error "Cannot read property 'map' of undefined". I would be very grateful for a suggestion how to improve it, how to make it work.
export default class Test extends Component {
state = {
dataTest: this.props.data.test.edges.map(({ node: test }) =>
test.section1Title),
dataTest2: this.props.data.test.edges.map(({ node: test }) =>
test.section2Lead),
dataTest3: this.props.data.test.edges.map(({ node: test }) =>
test.section1Text.json)
}
render() {
return <div>
<h1>{this.state.dataTest}</h1>
<h1>{this.state.dataTest2}</h1>
{documentToReactComponents(this.state.dataTest3)}
</div>
}
}
export const query = graphql`
{
test:allContentfulHomepage(limit: 1) {
edges {
node {
section1Title
section2Lead
section1Text {
json
}
}
}
}
}
`
If you're writing a page component as a class, you don't need to use the UseStaticQuery, you can use the simple PageQuery for this purpose.
To loop through arrays, the map() method works as well.
UPDATE
import React, { Component } from 'react';
import { graphql } from 'gatsby';
export default class Test extends Component {
render() {
const { edges } = this.props.data.test;
return (
<div>
{edges.map(({ node: itemFromContentful }) => (
<h1>{itemFromContentful.section1Title}</h1>
<h1>{itemFromContentful.section2Lead}</h1>
{documentToReactComponents(section1Text.json)}
))}
</div>
);
}
}
export const query = graphql`
{
test:allContentfulHomePage(limit: 1) {
edges {
node {
section1Title
}
}
}
}
`
Whats happening:
The GraphQL query you're using is bringing the data you want from the Contentful;
The React Stateful Component (class Test) is receiving all the data available from the query as a prop;
We're accessing this data on the render() method using the destructing assignment;
we're accessing the data nodes through the map method (the one I suggested you to take a look;
The curly braces into the JSX allows you to use JS to manipulate what you want - In this case, to render the information.
I have a real-time filter structure on a page. The data from my inputs are kept in my State but also as a URL query so that when someone opens the page with filters in the URL the right filters are already selected.
I'm struggling to find a stable way to keep these 2 in sync. Currently, I'm getting the data from my URL on load and setting the data in my URL whenever I change my state, but this structure makes it virtually impossible to reuse the components involved and mistakes can easily lead to infinite loops, it's also virtually impossible to expand. Is there a better architecture to handle keeping these in sync?
I would recommend managing the state of the filters in the view from query params. If you use react-router, you can use query params instead of state and in the render method get params need for view elements. After change filters you need implement redirect. For more convenience it may be better to use qs module. With this approach you will also receive a ready-made parameter for request to backend.
Example container:
const initRequestFields = {someFilterByDefault: ''};
class Example extends Component{
constructor(props) {
super(props);
this.lastSearch = '';
}
componentDidMount() {
this.checkQuery();
}
componentDidUpdate() {
this.checkQuery();
}
checkQuery() {
const {location: {search}, history} = this.props;
if (search) {
this.getData();
} else {
history.replace({path: '/some-route', search: qs.stringify(initRequestFields)});
}
}
getData() {
const {actionGetData, location: {search}} = this.props;
const queryString = search || `?${qs.stringify(initRequestFields)}`;
if (this.lastSearch !== queryString) {
this.lastSearch = queryString;
actionGetData(queryString);
}
}
onChangeFilters = (values) => {
const {history} = this.props;
history.push({path: '/some-route', search: qs.stringify(values)});
};
render() {
const {location: {search}} = this.props;
render(<Filters values={qs.parse(search)} onChangeFilers={this.onChangeFilters} />)
}
}
This logic is best kept in the highest container passing the values to the components.
For get more info:
Query parameters in react router
Qs module for ease work with query
If you worry about bundle size with qs module
This answer used React Hooks
You want to keep the URL with the state, you need a two way sync, from the URL to the state (when the component mount) and from the state to the URL (when you updating the filter).
With the React Router Hooks, you can get a reactive object with the URL, and use it as the state, this is one way- from URL to the component.
The reverse way- update the URL when the component changed, can be done with history.replace.
You can hide this two way in a custom hook, and it will work like the regular useState hook:
To use Query Params as state:
import { useHistory, useLocation} from 'react-router-dom'
const useQueryAsState = () => {
const { pathname, search } = useLocation()
const history = useHistory()
// helper method to create an object from URLSearchParams
const params = getQueryParamsAsObject(search)
const updateQuery = (updatedParams) => {
Object.assign(params, updatedParams)
// helper method to convert {key1:value,k:v} to '?key1=value&k=v'
history.replace(pathname + objectToQueryParams(params))
}
return [params, updateQuery]
}
To use Route Params as state:
import { generatePath, useHistory, useRouteMatch } from 'react-router-dom'
const useParamsAsState = () => {
const { path, params } = useRouteMatch()
const history = useHistory()
const updateParams = (updatedParams) => {
Object.assign(params, updatedParams)
history.push(generatePath(path, params))
}
return [params, updateParams]
}
Note to the history.replace in the Query Params code and to the history.push in the Route Params code.
Usage: (Not a real component from my code, sorry if there are compilation issues)
const ExampleComponent = () => {
const [{ user }, updateParams] = useParamsAsState()
const [{ showDetails }, updateQuery] = useQueryAsState()
return <div>
{user}<br/ >{showDetails === 'true' && 'Some details'}
<DropDown ... onSelect={(selected) => updateParams({ user: selected }) />
<Checkbox ... onChange={(isChecked) => updateQuery({ showDetails: isChecked} })} />
</div>
}
I published this custom hook as npm package: use-route-as-state
Say I have two redux connected components. The first is a simple todo loading/display container, with the following functions passed to connect(); mapStateToProps reads the todos from the redux state, and mapDispatchToProps is used to request the state to be provided the latest list of todos from the server:
TodoWidgetContainer.js
import TodoWidgetDisplayComponent from '...'
function mapStateToProps(state) {
return {
todos: todoSelectors.getTodos(state)
};
}
function mapDispatchToProps(dispatch) {
return {
refreshTodos: () => dispatch(todoActions.refreshTodos())
};
}
connect(mapStateToProps, mapDispatchTo)(TodoWidgetDisplayComponent);
The second redux component is intended to be applied to any component on a page so that component can indicate whether a global "loading" icon is displayed. Since this can be used anywhere, I created a helper function that wraps MapDispatchToProps in a closure and generates an ID for each component, which is used to make sure all components that requested the loader indicate that they don't need it anymore, and the global loader can be hidden.
The functions are basically as follows, with mapStateToProps exposing the loader visibility to the components, and mapDispatchToProps allowing them to request the loader to show or hide.
Loadify.js
function mapStateToProps(state) {
return {
openLoader: loaderSelectors.getLoaderState(state)
};
}
function mapDispatchToProps() {
const uniqId = v4();
return function(dispatch) {
return {
showLoader: () => {
dispatch(loaderActions.showLoader(uniqId));
},
hideLoader: () => {
dispatch(loaderActions.hideLoader(uniqId));
}
};
};
}
export default function Loadify(component) {
return connect(mapStateToProps, mapDispatchToProps())(component);
}
So now, if I have a component that I want to give access to the loader, I can just do something like this:
import Loadify from '...'
class DisplayComponent = new React.Component { ... }
export default Loadify(DisplayComponent);
And it should give it a unique ID, allow it to request the loader to show/hide, and as long as there is one component that is requesting it to show, the loader icon will show. So far, this all appears to be working fine.
My question is, if I would like to apply this to the todos component, so that that component can request/receive its todos while also being allowed to request the loader to show while it is processing, could I just do something like:
TodoWidgetContainer.js
import Loadify from '...'
import TodoWidgetDisplayComponent from '...'
function mapStateToProps(state) {
return {
todos: todoSelectors.getTodos(state)
};
}
function mapDispatchToProps(dispatch) {
return {
refreshTodos: () => dispatch(todoActions.refreshTodos())
};
}
const TodoContainer = connect(mapStateToProps, mapDispatchTo)(TodoWidgetDisplayComponent);
export default Loadify(TodoContainer);
And will redux automatically merge the objects together to make them compatible, assuming there are no duplicate keys? Or will it take only the most recent set of mapStateToProps/mapDispatchTo unless I do some sort of manual merging? Or is there a better way to get this kind of re-usability that I'm not seeing? I'd really rather avoid having to create a custom set of containers for every component we need.
connect will automatically merge together the combination of "props passed to the wrapper component", "props from this component's mapState", and "props from this component's mapDispatch". The default implementation of that logic is simply:
export function defaultMergeProps(stateProps, dispatchProps, ownProps) {
return { ...ownProps, ...stateProps, ...dispatchProps }
}
So, if you stack multiple levels of connect around each other , the wrapped component will receive all of those props as long as they don't have the same name. If any of those props do have the same name, then only one of them would show up, based on this logic.
Alright, here is what I would do. Create a higher order component (HOC) that adds a new spinner reference to your reducer. The HOC will initialize and destroy references to the spinner in redux by tying into the life cycle methods. The HOC will provide two properties to the base component. The first is isLoading which is a function that takes a boolean parameter; true is on, false is off. The second property is spinnerState that is a readonly boolean of the current state of the spinner.
I created this example without the action creators or reducers, let me know if you need an example of them.
loadify.jsx
/*---------- Vendor Imports ----------*/
import React from 'react';
import { connect } from 'react-redux';
import v4 from 'uuid/v4';
/*---------- Action Creators ----------*/
import {
initNewSpinner,
unloadSpinner,
toggleSpinnerState,
} from '#/wherever/your/actions/are'
const loadify = (Component) => {
class Loadify extends React.Component {
constructor(props) {
super(props);
this.uniqueId = v4();
props.initNewSpinner(this.uniqueId);;
this.isLoading = this.isLoading.bind(this);
}
componentWillMount() {
this.props.unloadSpinner(this.uniqueId);
}
// true is loading, false is not loading
isLoading(isOnBoolean) {
this.props.toggleSpinner(this.uniqueId, isOnBoolean);
}
render() {
// spinners is an object with the uuid as it's key
// the value to the key is weather or not the spinner is on.
const { spinners } = this.props;
const spinnerState = spinners[this.uniqueId];
return (
<Component isLoading={this.isLoading} spinnerState={spinnerState} />
);
}
}
const mapStateTopProps = state => ({
spinners: state.ui.spinners,
});
const mapDispatchToProps = dispatch => ({
initNewSpinner: uuid => dispatch(initNewSpinner(uuid)),
unloadSpinner: uuid => dispatch(unloadSpinner(uuid)),
toggleSpinner: (uuid, isOn) => dispatch(toggleSpinnerState(uuid, isOn))
})
return connect(mapStateTopProps, mapDispatchToProps)(Loadify);
};
export default loadify;
Use Case Example
import loadify from '#/location/loadify';
import Spinner from '#/location/SpinnerComponent';
class Todo extends Component {
componentWillMount() {
this.props.isLoading(true);
asyncCall.then(response => {
// process response
this.props.isLoading(false);
})
}
render() {
const { spinnerState } = this.props;
return (
<div>
<h1>Spinner Testing Component</h1>
{ spinnerState && <Spinner /> }
</div>
);
}
}
// Use whatever state you need
const mapStateToProps = state => ({
whatever: state.whatever.youneed,
});
// use whatever dispatch you need
const mapDispatchToProps = dispatch => ({
doAthing: () => dispatch(doAthing()),
});
// Export enhanced Todo Component
export default loadify(connect(mapStateToProps, mapDispatchToProps)(Todo));
I am using this starter kit https://github.com/davezuko/react-redux-starter-kit and am following some tutorials at the same time, but the style of this codebase is slightly more advanced/different than the tutorials I am watching. I am just a little lost with one thing.
HomeView.js - This is just a view that is used in the router, there are higher level components like Root elsewhere I don't think I need to share that, if I do let me know, but it's all in the github link provided above.
import React, { PropTypes } from 'react'
import { connect } from 'react-redux'
import { searchListing } from '../../redux/modules/search'
export class HomeView extends React.Component {
componentDidMount () {
console.log(this.props)
}
render () {
return (
<main onClick={this.props.searchListing}>
<NavBar search={this.props.search} />
<Hero/>
<FilterBar/>
<Listings/>
<Footer/>
</main>
)
}
}
I am using connect() and passing in mapStateToProps to tell the HomeView component about the state. I am also telling it about my searchListing function that is an action which returns a type and payload.
export const searchListing = (value) => {
console.log(value)
return {
type: SEARCH_LISTINGS,
payload: value
}
}
Obviously when I call the method inside the connect() I am passing in an empty object searchListing: () => searchListing({})
const mapStateToProps = (state) => {
return {
search: { city: state.search }
}
}
export default connect((mapStateToProps), { searchListing: () => searchListing({}) })(HomeView)
This is where I am stuck, I am trying to take the pattern from the repo, which they just pass 1, I think anytime that action is created the logic is just add 1 there is no new information passed from the component.
What I am trying to accomplish is input search into a form and from the component pass the users query into the action payload, then the reducer, then update the new state with the query. I hope that is the right idea.
So if in the example the value of 1 is hardcoded and passed into the connect() method, how can I make it so that I am updating value from the component dynamically? Is this even the right thinking?
You almost got it right. Just modify the connect function to pass the action you want to call directly:
const mapStateToProps = (state) => ({
search: { city: state.search }
});
export default connect((mapStateToProps), {
searchListing
})(HomeView);
Then you may use this action with this.props.searchListing(stringToSearch) where stringToSearch is a variable containing the input value.
Notice : You don't seem to currently retrieve the user query. You may need to retrieve it first and then pass it to the searchListing action.
If you need to call a function method, use dispatch.
import { searchListing } from '../../redux/modules/search';
const mapDispatchToProps = (dispatch) => ({
searchListing: () => {
dispatch(searchListing());
}
});
export default connect(mapStateToProps, mapDispatchToProps)(HomeView);
Then, you have made the function a prop, use it with searchListing.