React, render dinamic child passing prop - reactjs

I'm slowly getting introduced into advanced React and best practices for production apps. I want to know what is considered the "best practice" way of rendering a dinamic child passing props regarding maintainability, readability etc:
Here is the codeSandBox for more details
First method, render children as function passing the prop:
import { useState } from "react"
import "./Mouses.css"
export default function MouseChildren ({children}) {
const [mousePos, setMousePos] = useState(undefined)
function handleMouseMove(e) {
setMousePos({x: e.clientX, y: e.clientY})
}
/* this is bad because if more than one child is passed it will break */
return (
<div className="Mouse-container" onMouseMove={handleMouseMove}>
{children(mousePos)}
</div>
)
}
and then you call it this way:
<MouseChildren>
{position => <RandomDiv position={position} method="children as function method" />}
</MouseChildren>
Second method, render function as prop:
import { useState } from "react"
import "./Mouses.css"
export default function MouseRenderMethod ({render}) {
const [mousePos, setMousePos] = useState(undefined)
function handleMouseMove(e) {
setMousePos({x: e.clientX, y: e.clientY})
}
return (
<div className="Mouse-container" onMouseMove={handleMouseMove}>
{render(mousePos)}
</div>
)
}
and you call it like this:
<MouseRenderMethod render={position => <RandomDiv position={position} method="render as prop method:" />} />
and the third method is using React.Children:
import React, { useState } from "react"
import "./Mouses.css"
export default function MouseCreateComponent({children}) {
const [mousePos, setMousePos] = useState(undefined)
function handleMouseMove(e) {
setMousePos({x: e.clientX, y: e.clientY})
}
return (
<div className="Mouse-container" onMouseMove={handleMouseMove}>
{React.Children.map(React.Children.toArray(children), child => {
if (React.isValidElement(child)) return React.cloneElement(child, {position: mousePos}, null)
})}
</div>
)
}
and you call it like this:
<MouseCreateComponent>
<RandomDiv method="React.CloneElement method" />
</MouseCreateComponent>
I'm not sure which way is considered to be best over the others. If you from your experience can explain a bit

If you want to pass anything in your code without retyping it, you need to use useContext and the structure is:
One example with full-functionality which it may help you is:
const AppContext = React.createContext();
const AppProvider = ({ children }) => {
const [userData, setUserData]=useState('nothing here')
// any code you want to pass in the code
//e.g. a function
const randomFunction = ()=>{
//do something here
}
return (
<AppContext.Provider
value={{
userData,
setUserData,
randomFunction
}}
>
{children}
</AppContext.Provider>
);
};
export const useGlobalContext = () => {
return useContext(AppContext);
};
export { AppContext, AppProvider };
then all you have to do is to wrap all the components (children) you want, e.g. wrap <App /> so, more or less everything:
<AppProvider>
<App />
</AppProvider>
So now in this case you can use everything from your AppContext in all your code, you can pass more variables and functions if you want, and you import that by using:
import { useGlobalContext } from '/pathYouHaveIt';
function App() {
const {
userData,
setUserData,
randomFunction,
} = useGlobalContext();
// now you can use those like you have them set-up in the App()

Related

Push state of reactive value to dependencies

I have an object: dynamicJSON that is changing. I would like to pass this object down to multiple dependencies: componentA, componentB. I also want the parts of the dependencies using the object to render when the object is changed.
I tried the useContext Hook, but received a dependency cycle error. What is the proper way to pass reactive values down to dependencies in react?
App.js
import { componetA } from "compA"
import { componetB } from "compB"
import { fetchLatestValue} from "api/fetchLatestValue"
import { useEffect } from "react"
export default function App() {
const dynamicJSON = ???;
useEffect(() => {
let timeoutId;
async function getlatestValue() {
try {
const data = await fetchLatestValue();
// update dynamicJSON here.
} catch (error) {
}
timeoutId = setTimeout(getlatestValue, 1000 * 1);
}
getlatestValue();
return () => {
clearTimeout(timeoutId);
};
}, []);
return (
<componetA />
<componetB />
);
}
compA
export default function componentA() {
const dynamicJSON = ???;
return(
<div>
{dynamicJSON.value}
</div>
)
};
Have you tried useEffect() with a dependency array? If anything in the dependency array is changed, the hook function will be triggered.
Reference: https://reactjs.org/docs/hooks-reference.html#conditionally-firing-an-effect
Sorry I mis-read your question, you should pass dynamicJSON into both components as a prop. Make dynamicJSON a state is also a good idea.
Rule of thumb: if a prop or state of a component is changed, then this component is rerendered.
import { ComponentA } from "compA";
import { ComponentB } from "compB";
import { useEffect, useState } from "react";
export default function App() {
const [dynamicJSON, setDynamicJSON] = useState({});
//...omit
return (
<ComponentA dynamicJSON={dynamicJSON}/>
<ComponentB dynamicJSON={dynamicJSON}/>
);
}
CompA.js
export default function ComponentA(props) {
const { dynamicJSON } = props;
return(
<div>
{dynamicJSON.value}
</div>
)
};

How to call Parent containers function from child component in Reactjs

My application renders dynamic tiles by user and the tiles need to be re-arranged based on external configuration, StyleWrapper need to be a common container component that can be used in other projects too. Following is our UI component structure:
<StyleWrapper>
<Header />
<Content>
<PortletTiles />
</Content>
</StyleWrapper>
Given above structure, I have a function in StyleWrapper.tsx called arrangeTiles() which arranges tiles based on external configuration.
My question is how to call arrangeTiles() function from child component PortletTiles.tsx
StyleWrapper.tsx --> Parent Wrapper Container component
function StyleWrapper(props:any) {
let arrangeTiles= () => {
// Arrange Tile logic
};
return (
<div id="cnplStyleWrapper">
{props.children}
</div>
);
}
export default StyleWrapper;
PortletTiles.tsx --> Child component
function PortletTiles(props:any) {
let addNewTile= () => {
// Some logic to render tile here.. then
// How to call parent container's arrangeTiles function?
};
return (
<div>
<button onClick={addNewTile}>Add Tile</button>
</div>
);
}
export default PortletTiles;
You can create a context and pass the function or any other props as value. useContext can be used to consume the passed value from the provider.
type ContextValue = {
arrangeTiles: () => void;
};
const Context = createContext<ContextValue>({
arrangeTiles: () => {}
});
const StyleWrapper: FC = (props) => {
let arrangeTiles = () => {
// Arrange Tile logic
alert("Tiles have been arranged!");
};
return (
<Context.Provider value={{ arrangeTiles }}>
<div id="cnplStyleWrapper">{props.children}</div>
</Context.Provider>
);
};
const PortletTiles: FC = (props) => {
const { arrangeTiles } = useContext(Context);
let addNewTile = () => {
arrangeTiles();
};
return (
<div>
<button onClick={addNewTile}>Add Tile</button>
</div>
);
};
export default function App() {
return (
<StyleWrapper>
<PortletTiles />
</StyleWrapper>
);
}
If your app already uses redux, then you can move arrangeTiles to the reducer and dispatch actions from the components.
The easiest way to go about this would be to use context API. This provides you with the ability to have state that you can reference and manipulate across different components. You can reference the docs for context API here.
import { createContext } from "react";
const AppContext = createContext();
const AppProvider = (props) => {
const arrangeTiles = () => {
// place function code here
};
return (
<AppContext.Provider value={{arrangetiles}}>
{props.children}
</SiteContext.Provider>
);
};
const AppConsumer = AppContext.Consumer;
export { AppContext, AppConsumer };
export default AppProvider;
Wrap your app component in the AppProvider
In the child component, you would need to import the rearrangeTiles function and then you can use it:
import { useContext } from "react";
const { rearrangeTiles } = useContext(AppContext);

React native typescript: usecontext functions not firing from inside child component

I have an issue when i try to use functions from a context inside a child component in a React native android app.
Below is my code for the context, and the form component im using it in (stripped down for brevity).
The "isFormOpen" object can be read no problem from inside any children that is wrapped in the provider, but when i try to call the "toggleForm" function from the same child component, it does nothing, no console errors either.
I have another context which is identical in structure and syntax except for vairable and function names etc, and that works perfectly, so im a bit confused as to why this does not work. I removed the other context, thinking there might be some type of conflict, but didnt solve it.
AccountContext.tsx
import React, { FC, createContext, useContext, useState } from 'react';
interface AccountContextType {
isFormOpen: boolean,
toggleForm: (toggle: boolean) => void
};
export const AccountContext = createContext<AccountContextType>({
isFormOpen: false,
toggleForm: () => null
});
export const AccountContextProvider: FC = props => {
const [formOpen, setFormOpen] = useState<boolean>(false);
const toggleForm = (toggle: boolean) => {
setFormOpen(toggle);
}
const value: AccountContextType = {
isFormOpen: formOpen,
toggleForm
}
return (
<AccountContext.Provider value={value}>
{props.children}
</AccountContext.Provider>
)
}
export const useAccountContext = () => useContext(AccountContext);
TrackUploadForm.js
import React from 'react';
import { SafeAreaView } from 'react-native';
import { Button } from 'react-native-paper';
import { useAccountContext } from '../contexts/AccountContext';
import { AccountContextProvider } from '../contexts/AccountContext';
const TrackUploadForm = () => {
const accountContext = useAccountContext();
return (
<AccountContextProvider>
<SafeAreaView>
<Button onPress={() => accountContext.toggleForm(false)} mode='outlined'>Cancel</Button>
</SafeAreaView>
</AccountContextProvider>
)
};
export default TrackUploadForm;
useAccountContext is called outside the provider
export default function App() {
return (
<AccountContextProvider>
<Content />
</AccountContextProvider>
);
}
const Content = () => {
const accountContext = useAccountContext();
return (
<div className="App">
<h1>{accountContext.isFormOpen ? "true" : "false"}</h1>
<Button onPress={() => accountContext.toggleForm(false)} mode='outlined'>Cancel</Button>
</div>
);
};
accountContext.toggleForm(false) <-- always false, change it to accountContext.toggleForm(!accountContext.isFormOpen)
Together we have
https://codesandbox.io/s/cranky-panini-yo129

How to test code that uses a custom hook based on useContext with react-testing-library and jest

I've created a custom context hook - and I'm struggling to figure out how to pass values to its provider during testing.
My hook:
import React, { createContext, useContext, useState } from 'react';
const Context = createContext({});
export const ConfigurationProvider = ({ children }) => {
// Use State to keep the values
const [configuration, setConfiguration] = useState({});
// pass the value in provider and return
return (
<Context.Provider
value={{
configuration,
setConfiguration,
}}
>
{children}
</Context.Provider>
);
};
export const useConfigurationContext = () => useContext(Context);
export const { Consumer: ConfigurationConsumer } = Context;
This is how it's used in the application:
function App() {
return (
<ConfigurationProvider>
<div className="app">
<ComponentA />
</div>
</ConfigurationProvider>
);
}
And in ComponentA:
const ComponentA = () => {
// Get configuration
const configuration = useConfigurationContext();
return (
<div>{JSON.stringify(configuration)}</div>
)
}
This all works fine - considered that I'm calling setConfiguration from another component and set an object. Now for the testing part:
import React, { Component, createContext } from 'react';
import { render, waitFor } from '#testing-library/react';
import ComponentA from 'componentA';
const config = {
propertyA: 'hello',
};
test('renders the config', async () => {
const ConfigurationContext = createContext();
const { queryByText } = render(
<ConfigurationContext.Provider value={config}>
<ComponentA />
</ConfigurationContext.Provider>
);
expect(queryByText('hello')).toBeInTheDocument();
});
This doesn't work - I'm expecting the value that I'm sending in would be rendered in the div, but the context is an empty object. What am I doing wrong?
Thanks to Carle B. Navy I got the reason why it doesn't work. For other people two wonder what the solution is I fixed it by doing the following:
In my context hook, I changed the last line to export the provider as well:
export const { Consumer: ConfigConsumer, Provider: ConfigProvider } = Context;
Then in my test case, instead of creating a new context, I import the ConfigProvider at the top, and then:
const { queryByText } = render(
<ConfigProvider value={config}>
<ComponentA />
</ConfigProvider>
);
Thanks for helping me solve this and hope this helps someone else.

Consumer not reading provider value

This is my Provider component
import React, {useState} from 'react';
import MyComponent from './MyComponent/MyComponent';
export default function App(props) {
const [header, setHeader] = useState(null);
function setHeaderText(header) {
setHeader(header);
}
const contextItems = {
setHeaderText: setHeaderText,
what: 'what'
};
return <React.Fragment>
<AppContext.Provider value={contextItems}>
<MyComponent request={props.request}/>
</AppContext.Provider>
</React.Fragment>
}
export const AppContext = React.createContext({
setHeaderText: () => {},
what: ''
});
I am using the consumer to try and pass the context value to MyComponent which is a functional component.
function MyComponent() {...}
export default (props) => {
return <AppContext.Consumer>
{ value => <MyComponent context={value} {...props} />}
</AppContext.Consumer>
}
My issue is that it only passes the default value of AppContext not the value that i am passing in.
Can anyone see anything wrong with my code at all?
I am using the latest version of react 16.8.2.
Thanks for any help.
Side note: I know i could just use composition in this case but the header value will be used throughout the app.

Resources