React children's when using HOC to wrap parent - react-i18next

I am using React 16.8.6 and I have the following structure:
page.js
<ParentComponent id="testData">
<ChildComponent value={data => data.text} />
</ParentComponent>
parentComponent.tsx
export default class ParentComponent extends React.PureComponent<IParentProps> {
...
render() {
const items = this.props.children;
<MiddleComponent items={items} />
}
}
ParentContainer.ts
import { withTranslation } from 'react-i18next';
import ParentComponent from './ParentComponent';
export default withTranslation()(ParentComponent);
I need to know inside of MiddleComponent the element type (not as a String but as a React element since I am going to create a new Element based on it) of each child (so, in this case I should have ChildComponent), but when I inspect with chrome, all my children have a I18nextWithTranslation type...
Any idea how to fix this? Or if this is maybe a known bug?
If I don't use any hoc at all, when I write child.type it returns me ChildComponent(props). But this is not true to when I am using hocs to wrap the parent...

The issue was very stupid...
I was importing the <ChildComponent> as a default import even though the child was not exported as default.
Basically
import ChildComponent from '' instead of import { ChildComponent } from ''

In the example below, we're setting Component.displayName on our components so we can access that property in parents. This is a super trivial example that could be expanded to work with an array of children if needed.
const ChildComponent = () => {
return <div>child render</div>
}
ChildComponent.displayName = "MyComponentName"
const ParentComponent = ({ children }) => {
// This is the type of component.. should output "MyComponentName"
const childType = children.type.displayName
return (
<div>
<h1>Render Children</h1>
{children}
</div>
)
}
function App() {
return (
<ParentComponent>
<ChildComponent />
</ParentComponent>
)
}

Related

Access component parameters inside caller component

I have a component which I call inside another component as:
COMPONENT 1
<template>
<FiltersComponent />
</template>
export default Vue.extend({
components: { FiltersComponent }
)}
So, this FiltersComponents have some parameters I want to access into my component one
COMPONENT 2 DATA
data() {
return {
TestList: [] as string[],
Test2List: null as string[] | null,
}
},
How can I access that TestList and Test2List inside COMPONENT 1?
There are multiple possibilities: If one component is a child component or sibling of the other, you might want to take a loop at props (passing data down) and events (passing data up). Otherwise, if they are not siblings or children, you can use a store like vuex.
To use the docs example:
vue entry point: (e.g., app.js):
import { createApp } from 'vue'
import { createStore } from 'vuex'
// Create a new store instance.
const store = createStore({
state () {
return {
someProperty: 'someValue'
}
},
mutations: {
update (state, value) {
state.someProperty = value
}
}
})
const app = createApp({ /* your root component */ })
// Install the store instance as a plugin
app.use(store)
In your component's script-section:
this.$store.commit('update', 'YOUR_VALUE')
Other component:
const val = this.$store.state.someProperty
However, this is only a very basic example. You should definitely check out the docs, especially the sections about state, getters and mutations.
Always store the state as high in the component tree as you need, meaning that if you need these lists inside component 1, store them there. Then, you can use props and events to access and update the data inside component 2.
Alternatively, you can use a centralized store like Vuex.
You can achieve the same using ref,
Check the below example
<template>
<FiltersComponent ref="childComponent" /> <!-- Adding ref over here -->
</template>
export default Vue.extend({
components: { FiltersComponent }
)}
then in any of your methods or mounted section in Component 1. You can access Component2 data like
this.$refs.childComponent.TestList
or
this.$refs.childComponent.Test2List
So simple isn't it? ;)

useContext does not forward dispatch function from useReducer

I am trying not to use Redux. So I am stuck with useContext in combination with useReducer for globaal state management. My problem: I can not update the state from a child component using dispatch.
Let me explain in more detail. My context file is pretty straight forward:
import React, { createContext } from "react";
const ActivateContext = createContext();
export default ActivateContext;
I import it in App.js and wrap it around the root component within my navigation:
import React, { useState, useReducer } from "react";
import Navigation from "./Navigation";
import ActivateContext from "./store/activate-context";
const Reducer = (state, action) => {
if (action.type === "ACTIVATE_IT") return true;
};
export default function App() {
let initialState = false;
const [state, dispatch] = useReducer(Reducer, initialState);
return (
<Provider store={store}>
<ActivateContext.Provider value={{activeStatus: state, activeDispatch: dispatch}}>
<Navigation />
</ActivateContext.Provider>
</Provider>
);
I then import "ActivateContext" in my child component called "Child". I save everything in the constant "activated". I then use "activated" in the prop called "access":
import React, {useContext} from "react";
import ActivateContext from "../../../store/activate-context";
function Child (props) {
const activated = useContext(ActivateContext);
<MightComponent title="So Amazing" access={activated} />
I tried to add a button to the component "Child" to change the state in App.js but nothing happens:
<TouchableOpacity
onClick={() => ActivateContext.activeDispatch("ACTIVATE_IT")}
>
<Text>Testit</Text>
</TouchableOpacity>
I know useContext works. If I i set "intitialState" to true in App.js and give it as a value to my provider, the "access" prop in the Child component receives "true", which makes the component change its style:
<ActivateContext.Provider value={initialState}>
<Navigation />
</ActivateContext.Provider>
However I do not manage to use useContext to also pass down the dispatch function down the component tree...
Any help is much appreciated.
Thanks!
I think you're trying to access your context values incorrectly in your onClick function, here:
onClick={() => ActivateContext.activeDispatch("ACTIVATE_IT")}
You're passing an object with two fields to your value prop:
<ActivateContext.Provider value={{activeStatus: state, activeDispatch: dispatch}}>
<Navigation />
</ActivateContext.Provider>
So you should be able to access both of these values, in your pages, doing something like:
const {activeStatus, activeDispatch} = useContext(ActivateContext);
And, since your dispatch expects an object with a type field, your onClick function would be something like:
onClick={() => activeDispatch({type: "ACTIVATE_IT"})}

React native MobX store - MobX injector: Store is not available

I'm using the following template: https://github.com/cawfree/create-react-native-dapp
I have used the provider component in the top level (parent) component like so:
import { Provider } from 'mobx-react';
import SettingsStore from '../../store/settings';
return (
<Provider store={SettingsStore}>
<View style={[StyleSheet.absoluteFill, styles.center, styles.white, !loading && {justifyContent: 'space-between'}]}>
{loading ?
<FadeOutImage />
:
<Welcome />
}
</View>
</Provider>
);
Here's my SettingsStore in file settings.js:
import {makeAutoObservable} from 'mobx';
class SettingsStore {
darkMode = false
constructor() {
makeAutoObservable(this)
}
toggleDarkMode = () => {
this.darkMode = !this.darkMode
}
}
export default new SettingsStore();
The following is where I am injecting the store. It's the child component <Welcome />:
import { inject, observer } from "mobx-react";
const Welcome () => {
return (<View><Text>test</Text></View>)
}
export default inject("SettingsStore")(observer(Welcome));
I have checked that the paths for the imports is correct (would get another error otherwise). I just cannot understand why I am seeing the following err:
What's going wrong and how can I fix this?
You passing in under the name store into Provider (<Provider store={SettingsStore}>), so when injecting you need to use same prop name inject("store"). Or change prop name to SettingsStore: <Provider SettingsStore={SettingsStore}>

React Relay createFragmentContainer and QueryRenderer data flow

I've been working through the examples in relay's document for QueryRenderer https://relay.dev/docs/en/query-renderer and FragmentContainer https://relay.dev/docs/en/fragment-container
I'm confused as to how the data is meant to be accessed by the Components wrapped in the HOC createFragmentContainer.
I have a top level QueryRenderer:
function renderFunction({error, props}) {
...
if (props) {
// Added todoList as a prop with a distinct name from tdlist which
// is meant to be passed in by createFragmentContainer
return <TodoList todoList = {props.todoList} />
}
...
}
export default function ContainerWithQueryRenderer() {
return (
<QueryRenderer
environment={environment}
query={graphql`
query ContainerWithQueryRenderer_Query {
todoList {
title
todoListItems { text isComplete}
}
}`}
render = { renderFunction }
/>
);
}
and a TodoList component that defines what data is needed in a graphql fragment:
import React from 'react';
import TodoItem from './TodoItem.js';
import { createFragmentContainer } from 'react-relay';
import graphql from 'babel-plugin-relay/macro';
function TodoList(props) {
console.log('TodoList props:',props);
if (props && props.todoList && props.todoList.todoListItems && props.todoList.title) {
return (
<div className='list-container'>
<div className='list-header'>
<div className='list-header-label'>{props.todoList.title}</div>
</div>
<div className='list'>{props.todoList.todoListItems.map( (item, key) => <TodoItem key = {key} item={item} />)}</div>
</div>
);
} else {
return null;
}
}
export default createFragmentContainer(TodoList,{
tdlist: graphql`
fragment TodoList_tdlist on TodoList {
title
todoListItems {
...TodoItem_item
}
}
`,
})
and a child TodoListItem
import React from 'react';
import { createFragmentContainer } from 'react-relay';
import graphql from 'babel-plugin-relay/macro';
function TodoItem(props) {
return <div className='list-item'>
<div className='list-item-label'>{props.item.text}</div>
{props.item.isComplete ?<div className='list-item-buttons'>Done</div>:null}
</div>
}
export default createFragmentContainer( TodoItem,{
item: graphql`
fragment TodoItem_item on Todo {
text isComplete
}
`
});
My understanding is that the createFragmentContainer for the TodoList will inject the data from the TodoList_tdlist fragemnt as the TodoList props.tdlist), shaped as per the shape of the graphql query.
However, this appears to not be happening. I get a warning in the console:
Warning: createFragmentSpecResolver: Expected prop `tdlist` to be supplied to `Relay(TodoList)`, but got `undefined`. Pass an explicit `null` if this is intentional.
What is the job of the createFragmentContainer if it is not to pass in the tdlist?
I tried to pass the todoList data explicitly in by changing
return
to
return
(the same prop name passed in by createFragmentContainer, I (understandably) get a different error:
Warning: RelayModernSelector: Expected object to contain data for fragment `TodoList_tdlist`, got `{"title":"Your to-do list","todoListItems":[{"text":"Brush teeth","isComplete":false},....
I think the root of my confusion is not understanding how the fragments that define the data dependencies interact with the QueryRenderer. Do I need to define the query to pull in every possible piece of data that could ever be needed, and the point is that relay will only query what is needed by looking at the graphql fragments of the components that are being rendered now, and will re-query if that changes, updating props as it gets new data?
Do I need to pass props down to fragment containers as props explicitly, or if they are a descendant of a QueryRenderer that requests their data, will the createFragmentContainer be able to access it via the relay environment?
Here is my graphql.schema to assist:
type Query {
todoList: TodoList!
}
type Todo {
text: String!
isComplete: Boolean!
}
type TodoList {
title: String!
todoListItems: [Todo]!
}
You must name the descendant fragment container's GraphQL fragment in your QueryRenderer. Without identifying the fragment, react-relay has relay no link between the QueryRenderer and the descendant FragmentContainer components. In your question, the query prop passed to QueryRenderer is
query ContainerWithQueryRenderer_Query { todoList { title todoListItems { text isComplete }}}
instead of
query ContainerWithQueryRenderer_Query { TodoList_tdlist }
Also, because the QueryRenderer works hand in hand with the Fragment Containers, it is simpler to iterate the todoListItems in the Query Renderer, and use a fragment for the TodoItem_item. Hence below I have merged the above <ContainerWithQueryRenderer /> and <TodoList />
This approach works:
TodoListWithQueryRenderer.js:
function renderFunction({error, props}) {
...
if (props) {
return (
<div>
<div>
<div>{props.todoList.title}</div>
</div>
<div>{props.todoList.todoListItems.map( (item, key) => <TodoItem key = {key} item={item} /> )}</div>
</div>
);
}
...
}
export default function TodoListWithQueryRenderer() {
return (
<QueryRenderer
environment={environment}
query={graphql`
query TodoListWithQueryRenderer_Query { todoList {todoListItems {...TodoItem_item } title} }
`}
render = { renderFunction }
/>
);
}
with only one descendant component required as the above merges the ContainterWithQueryRenderer with TodoList components.
TodoItem.js:
import React from 'react';
import { createFragmentContainer } from 'react-relay';
import graphql from 'babel-plugin-relay/macro';
function TodoItem(props) {
return <div>
<div>{props.item.text}</div>
{props.item.isComplete ? <div>Done</div> : null}
</div>
}
export default createFragmentContainer( TodoItem,{
item: graphql`
fragment TodoItem_item on Todo {
text isComplete
}
`
});

onDismiss function in snackbar doesn't work in react-native-paper

I'm building mobile application with react-native and react-native-paper.
And I'm using SnackBar component in react-native-paper, and if I use SnackBar component directly, onDismiss function in SnackBar component works well. (It means the snackbar will disappear correctly)
But if I use my original component(like SnackBarComponent component) which uses SnackBar component provided react-native-paper, somehow, the snackbar will not disappear correctly.
This is my custom SnackBar Component and the code which calls my original SnackBar Component.
My original SnackBar Component
import React, { Component } from 'react';
import { Text } from 'react-native';
import { Provider, Snackbar } from 'react-native-paper';
export default class SnackBarComponent extends Component {
constructor(props) {
super(props);
this.state = {
snackbarVisible: false
}
}
render() {
return(
<Provider>
<Snackbar
visible={this.props.snackbarVisible}
onDismiss={() => this.setState({ snackbarVisible: false })}
>
<Text>{this.props.snackbarText}</Text>
</Snackbar>
</Provider>
)
}
}
The code which calls SnackBarComponent(This is not whole code)
import SnackBarComponent from './components/SnackBarComponent';
:
handleShowSnackbar() {
this.setState({
snackbarVisible: true,
snackbarText: 'show snackbar'
})
}
:
<SnackBarComponent snackbarVisible={this.state.snackbarVisible} snackbarText={this.state.snackbarText}/>
:
You have a state containing snackbarVisible which is local to SnackBarComponent and it is initially false.
Then you have snackbarVisible in the parent component state where it's local to the parent component. It is not the same as snackbarVisible in SnackBarComponent.
In case you did not specifically defined a state in parent component containing snackbarVisible, please note that when you run setState method it will create snackbarVisible in the state if not found one.
When you are updating snackbarVisible(dismiss in this case) you have to update the one you defined here visible={this.props.snackbarVisible} which is containing the snackbarVisible in the parent component through the props. Which means you have to update the parent component's snackbarVisible. For that you can pass a callback to the SnackBarComponent and update the right value in the parent component. Here's an example:
//parent component
import SnackBarComponent from './components/SnackBarComponent';
:
handleShowSnackbar() {
this.setState({
snackbarVisible: true,
snackbarText: 'show snackbar'
})
}
//add a function to update the parents state
handleDismissSnackbar = () => {
this.setState({
snackbarVisible: false,
})
}
:
<SnackBarComponent snackbarVisible={this.state.snackbarVisible}
snackbarText={this.state.snackbarText}
dismissSnack={this.handleDismissSnackbar}/> //add here
Then the children component SnackBarComponent in this case as follows:
import React, { Component } from 'react';
import { Text } from 'react-native';
import { Provider, Snackbar } from 'react-native-paper';
export default class SnackBarComponent extends Component {
//you dont need to maintain this local state anymore for this purpose
/*constructor(props) {
super(props);
this.state = {
snackbarVisible: false
}
}*/
render() {
return(
<Provider>
<Snackbar
visible={this.props.snackbarVisible}
onDismiss={() => this.props.dismissSnack()} //use that function here
>
<Text>{this.props.snackbarText}</Text>
</Snackbar>
</Provider>
)
}
}
Now when you press dismiss, it will call the handleDismissSnackbar in parent component by dismissSnack passed through the props.
this is controlling from parent. Example of controlled components. You can find about it more here: https://reactjs.org/docs/forms.html#controlled-components